http://codeforces.com/problemset/problem/401/D
题目大意:给定一个数字n,将n的每一位数字重新排列,求在这些排列数之中可以被n整除的方法数。
解题思路:
暴力超时……
大多数人的写法是进行位压缩,不过那样的话需要2^18*100 的空间,效率比较低,重复状态数较多,处理起来也不方便,这一题是给出了512M的空间。但是如果空间再小一倍,前者的方法就无能为力了。
发现有一种对于数位dp来说比较好的状态压缩方式,直接根据数码x出现的次数进行状态压缩。比如说333444,如果用前者的方法压缩就需要2^6=64的空间,而直接按照出现次数压缩就只需要3*3的空间,对于极限数据,利用均值不等式,也差不多只需(ceil(18/10+1)^10)=59049的空间,提高了空间的利用率(原来是2^18=262144)与判重的效率。
dp方程也很容易建立对于编码i所有对n取余得j的数字 其余数乘10加上下一位数字得到下一种状态的一部分。结果就是全部数字都使用的余为0的情况(dp[s][n])。
这种压缩的关键在于codeit和decode函数
code
#include <cstdio> #include <cstring> #include <cmath> #include <map> #define SIZES 40000 using namespace std; char n[20]; int c[10],d[10],tim[10],m; long long dp[60000][100]; int len; int codeit(int *te) { int res=0; for (int i=0;i<10;i++) res=res*(tim[i]+1)+te[i]; return res; } long long decode(int l,int *t) { for (int i=9;i>=0;i--){ t[i]=(l%(tim[i]+1)); l/=(tim[i]+1); } } int main() { int s; while(~scanf("%s%I64d",n,&m)){ memset(dp,0,sizeof dp); len=strlen(n); memset(tim,0,sizeof(tim)); for (int i=0;i<len;i++) ++tim[n[i]-'0']; int s=codeit(tim); // printf("%d\n",s); dp[0][0]=1; for (int i=0;i<s;i++){ int t[10]; decode(i,t); for (int j=0;j<10;j++) if (i+j) if (tim[j]-t[j]){ t[j]++; int pt=codeit(t); for (int k=0;k<m;k++) dp[pt][(k*10+j)%m]+=dp[i][k]; t[j]--; } } printf("%I64d\n",dp[s][0]); } }
[Codefoces 401D]Roman and Numbers 数位dp,布布扣,bubuko.com
[Codefoces 401D]Roman and Numbers 数位dp
原文地址:http://blog.csdn.net/ahm001/article/details/37593031