1072: [SCOI2007]排列perm
Time Limit: 10 Sec Memory Limit: 128 MB
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
Sample Output
1
3
3628800
90
3
6
1398
HINT
在前三个例子中,排列分别有1, 3, 3628800种,它们都是1的倍数。
【限制】
100%的数据满足:s的长度不超过10, 1<=d<=1000, 1<=T<=15
思路:
s的长度不超过10,算法就大概确定了。
f[i][j]表示选取i状态的数且此时的mod为j。
对于每个状态i,可能从它把的某一个二进制位置为1的位置变为零的状态转移过来(当前选了x个数有a种排列方式使得mod为k,那么在剩下的数字之中任选一个加在排列的最后,那么选x+1个数使得mod为k*10+这个数的值,就多了a种排列方式。代码注释中会解释。),这样不是特别容易操作,所以考虑逆向更新。
dp方程:f[(i|(1<<(k−1)))][(j∗10+a[k])%mod]+=f[i][j];条件是( i&(1<<(k-1)) )==0;
但是如果有重复的数字的话,我们会重复计算,所以除掉重复的数的个数的排列。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 15
#define M 1100
using namespace std;
int mod, lens, lim;
int a[N], cnt[N];
char s[N];
int f[1<<10][M];//f[i][j]表示选取i状态的数且此时的mod为j
//(eg:i=5时,选第1和第3个数,i=9时,选第1和第4个数,i=10时,选第2和第4个数)
int J[]={0,1,2,6,24,120,720,5040,40320,362880,3628800};//阶乘
void dfs(){
for(int i=0; i<lim; i++)
for(int k=0; k<mod; k++)
if( f[i][k] )
for(int j=0; j<lens; j++)
if( (i & (1<<j)) == 0 )
f[(i|(1<<j))][(k*10+a[j+1])%mod] += f[i][k];//a[j+1]放在最后
//对于状态i,排列的最后一位可能是选取了的数中的任意一个(前面的排列已经统计好了)
//为什么只是加在最后而不是其他地方,因为加在其他地方的情况会在加上它的最后一位的计算中被统计
}
int main(){
int T;scanf("%d", &T);
while( T-- ){
memset(cnt, 0, sizeof(cnt));
memset(f, 0, sizeof(f));
scanf("%s", s);
lens = strlen(s);
for(int i=0; i<lens; i++){
a[i+1] = s[i] - '0';
cnt[a[i+1]]++;
}
scanf("%d", &mod);
lim = (1<<lens);
f[0][0] = 1;
dfs();
int ans = f[lim-1][0];//所有数字用完
for(int i=0; i<=9; i++) if( cnt[i] ) ans /= J[cnt[i]];//相同的数字调换位置本质不变
printf("%d\n", ans);
}
return 0;
}