排列

题目描述

给一个数字串\(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;
}
posted @ 2020-06-27 14:58  hyskr  阅读(187)  评论(0编辑  收藏  举报