题目大意:给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0)。
题解:考虑数据较小,直接用STL的next_permutation加map判重就能过了………………
考虑状压dp,
用f[i][j]表示状态为i,余数为j的方案数
按照一般的想法,对于状态i,可能从它某一为1的位数变为零的状态转移过来,这样或许可做。不过,我们更加熟悉的操作是加上而非从原状态减去某个位,所以思路又转向逆向更新,枚举加入新的一位即可
f[i|(1<<k)][(j∗10+a[k]) % d]+=f[i][j], ((i & (1< <k))==0) <script id="MathJax-Element-436" type="math/tex">
去重:由于原排列中的数字可能有重复的,所有有一些重复的方案
如果某个数字 i 在排列中出现了 c[i] 次,那么最后的答案
ans/=c[i]的阶乘
我的收获:整除转化取余,对转移的思考方式
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int M=15;
int n,d,T,ans;
int a[M],c[M],f[1024+5][1005];
char s[15];
void work()
{
for(int i=0;i<(1<<n);i++)
for(int j=0;j<d;j++)
for(int k=0;k<n;k++)
if((i&(1<<k))==0)//状态i加上第k位的1
f[i|(1<<k)][(j*10+a[k])%d]+=f[i][j];//加入第k个数字
ans=f[(1<<n)-1][0];
for(int i=0;i<=9;i++) for(int j=1;j<=c[i];j++) ans/=j;
printf("%d\n",ans);
}
void init()
{
memset(c,0,sizeof(c));
memset(f,0,sizeof(f));
scanf("%s%d",s,&d);
n=strlen(s);
for(int i=0;i<n;i++) a[i]=s[i]-48,++c[a[i]];
f[0][0]=1;
}
int main()
{
cin>>T;
while(T--){
init();
work();
}
return 0;
}