bzoj1072[SCOI2007]排列perm
Description
给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0)。例如123434有90种排列能
被2整除,其中末位为2的有30种,末位为4的有60种。
Input
输入第一行是一个整数T,表示测试数据的个数,以下每行一组s和d,中间用空格隔开。s保证只包含数字0, 1
, 2, 3, 4, 5, 6, 7, 8, 9.
Output
每个数据仅一行,表示能被d整除的排列的个数。
Sample Input
7
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29
Sample Output
1
3
3628800
90
3
6
1398
3
3628800
90
3
6
1398
HINT
在前三个例子中,排列分别有1, 3, 3628800种,它们都是1的倍数。
【限制】
100%的数据满足:s的长度不超过10, 1<=d<=1000, 1<=T<=15
题解
这道题的做法不就是枚举全排列然后暴力哈希判断吗/滑稽
(以上做法纯属口胡)
我们一看到字符串的长度小于等于十,就很自然地想到要么是搜索,要么是状压。显然这道题去写搜索全排列的话复杂度一定会爆炸。因此我们考虑采用状压dp。我们定义f[i][j]表示当前状态为i,余数为j的情况有多少种,其中i是一个二进制数。二进制下i的第k位为1表示原来排列中的第k个数已经被使用过了,否则就没有使用过。对于转移,我们考虑每一次选择一个还没有使用过的原本在第j位的数x,将其作为最后一位,那么之后的状态就是(i|(1<<(j-1))),若原来的余数为y,那么新的余数必定为((y*10+x)%d)。其中d在题目中已经给出。因此我们就可以把f[i][j]转移到f[i|(1<<(k-1))][(j*10+a[k])%d]。其中k表示选择作为最后一位的数原来的位置,a[k]表示选择作为最后一位的数。即
f[i|(1<<(k-1))][(j*10+a[k])%d]=f[i|(1<<(k-1))][(j*10+a[k])%d]+f[i][j]。
代码
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<algorithm> 6 #include<cmath> 7 using namespace std; 8 int dp[(1<<10)+10][1010]; 9 int tp[20],a[20],re[20],va[20]; 10 int len,t,d; 11 char ch[20]; 12 int main(){ 13 int i,j,k;tp[0]=1; 14 for(i=1;i<=10;++i) tp[i]=(tp[i-1]<<1); 15 scanf("%d",&t); 16 for(int cas=1;cas<=t;++cas){ 17 scanf("%s",ch); 18 scanf("%d",&d); 19 len=strlen(ch); 20 for(i=0;i<=9;++i){ 21 re[i]=0;va[i]=1; 22 } 23 for(i=0;i<len;++i){ 24 a[i]=ch[i]-48; 25 re[a[i]]++;va[a[i]]=va[a[i]]*re[a[i]]; 26 } 27 for(i=0;i<tp[len];++i){ 28 for(j=0;j<d;++j) dp[i][j]=0; 29 } 30 dp[0][0]=1; 31 for(i=0;i<tp[len];++i){ 32 for(j=0;j<d;++j){ 33 if(dp[i][j]){ 34 for(k=0;k<len;++k){ 35 if(!(tp[k]&i)){ 36 dp[i|tp[k]][(j*10+a[k])%d]+=dp[i][j]; 37 } 38 } 39 } 40 } 41 } 42 for(i=0;i<=9;++i) dp[tp[len]-1][0]/=va[i]; 43 printf("%d\n",dp[tp[len]-1][0]); 44 } 45 return 0; 46 }