http://codeforces.com/problemset/problem/401/D
题目大意:给定一个数字n,将n的每一位数字重新排列,求在这些排列数之中可以被n整除的方法数。
解题思路:
暴力超时……
大多数人的写法是进行位压缩,不过那样的话需要2^18*100 的空间,效率比较低,重复状态数较多,处理起来也不方便,这一题是给出了512M的空间。但是如果空间再小一倍,前者的方法就无能为力了。
发现有一种对于数位dp来说比较好的状态压缩方式,直接根据数码x出现的次数进行状态压缩。比如说333444,如果用前者的方法压缩就需要2^6=64的空间,而直接按照出现次数压缩就只需要(3+1)*(3+1)=16的空间,对于极限数据,利用均值不等式,也差不多只需(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]);
}
}