题目描述
给一个数字串(s)和正整数(d), 统计(s)有多少种不同的排列能被(d)整除(可以有前导(0))。
例如(123434)有(90)种排列能被(2)整除,其中末位为(2)的有(30)种,末位为(4)的有(60)种。
输入格式
输入第一行是一个整数,表示测试数据的个数,以下每行一组 和 ,中间用空格隔开。保证只包含数字 .
输出格式
每个数据仅一行,表示能被 整除的排列的个数。
样例
样例输入
7
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29
样例输出
1
3
3628800
90
3
6
1398
数据范围与提示
样例说明:
在前三个例子中,排列分别有(1,3,3628800)种,它们都是(1)的倍数。
数据范围:
20%的数据满足:(s)的长度不超过5,1<=T<=5
50%的数据满足:(s)的长度不超过8
100%的数据满足:(s)的长度不超过10,1<=d<=1000,1<=T<=15
题解
- 状压dp,关于状压dp,老师给出了一个很好的判断方法,看数据范围,一般状压dp的数据范围都很小,都在20以内。这是由于状压要开的数组很大。
关于本题,不好想,但是很好理解。 - dp数组维二维数组,第一维是状态,这里是拿二进制来存储的,表示每一位表示对应该位置的数是否已经添加。如果是1,就是已经添加,如果为0,就是还未添加。由于10位数,所以数组要开到(1<<10)。第二维是余数。
- 第一维循环为状态,第二维循环为余数,第三维为要添加的数。
- 状态转移方程(dp[i|1<<k][(j*10+k)%d] += dp[i][j]),这里面第一维([1|1<<k])为加上k的位置,第二维显然是余数。状压dp的根本为递推,这个式子不难理解。(因为这个递推式中,下标有集合而不是整数,因此需要处理,把这个状态压缩成一个整数,这就是状压dp的名字由来)
- 转移条件:判断此为是否已经转移过了,如果转移过了,就不需要进一步进行转移。所以式子为
if((i&(1<<k))==0)
,如果判断结果为1,即已经转移过,为0,则执行语句,进行状态转移。 - 此题注意可以重复选择数字,根据数学知识,如果有数字重复,则除以该数字的阶乘。代码如下:
- 不回位运算者:请移步 传送门~~~
code
#include<bits/stdc++.h>
using namespace std;
#define cle(a) memset(a,0,sizeof(a));
const int maxn=1e5;
int a[maxn],dp[1<<10][1001],cnt[15];
int jc(int x){
int ans=1;
for(int i=1;i<=x;i++) ans*=i;
return ans;
}
int main(){
int T;cin>>T;
string s;int d;
while(T--){
cin>>s>>d;
int len=s.length();
cle(cnt) cle(dp)
for(int i=0;i<len;i++){
a[i]=s[i]-'0';
cnt[a[i]]++;
}
int maxs=(1<<len)-1;
dp[0][0] = 1;
for(int i=0;i<=maxs;i++){
for(int j=0;j<d;j++){
if(dp[i][j])
for(int k=0;k<len;k++)
if((i & (1<<k))==0)
dp[i|(1<<k)][(j*10+a[k])%d]+=dp[i][j];
}
}
int ans=dp[maxs][0];
for(int i=0;i<=9;i++)
if(cnt[i])
ans/=jc(cnt[i]);
cout<<ans<<endl;
}
return 0;
}