BZOJ 1072 排列

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 \le d \le 1000, 1 \le T \le 15$

\(f[i][j]\)表示状态为\(i\)(用二进制表示,若某位为\(1\),则该位在排列中)余数为\(j\)的方案数。于是,我们可以枚举排列长度,利用已知排列进行转移(应该很好YY,脑补一下)。代码应该好理解。

#include<vector>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;

#define maxn (12)
#define maxm (1010)
char s[maxn]; int n,m,f[1<<maxn][maxm],mi[maxn],jie[maxn]; vector <int> vec[maxn];

inline void dfs(int now,int bin,int have)
{
	if (now > n) { vec[have].push_back(bin); return; }
	dfs(now+1,bin<<1|1,have+1); dfs(now+1,bin<<1,have);
}

inline void dp()
{
	for (int i = 0;i <= n;++i) vec[i].clear();
	dfs(1,0,0);
	mi[0] = 1; 
	for (int i = 1;i <= n;++i) mi[i] = 10*mi[i-1]%m;
	memset(f,0,sizeof(f));
	for (int i = 1;i <= n;++i) f[1<<(i-1)][(s[i]-'0')%m] = 1;
	for (int i = 2;i <= n;++i)
	{
		int ni = vec[i].size();
		for (int ii = 0;ii < ni;++ii)
		{
			int p = vec[i][ii];
			for (int j = 1;j <= n;++j)
				if (p & (1<<(j-1)))
				{
					int q = p^(1<<(j-1));
					for (int k = 0;k < m;++k)
						f[p][(k+mi[i-1]*(s[j]-'0')%m)%m] += f[q][k];
				}
		}
	}
	for (int i = '0';i <= '9';++i)
	{
		int cnt = 0;
		for (int j = 1;j <= n;++j) cnt += s[j] == i;
		f[(1<<n)-1][0] /= jie[cnt];
	}
}

int main()
{
	freopen("1072.in","r",stdin);
	freopen("1072.out","w",stdout);
	jie[0] = 1;
	for (int i = 1;i <= 10;++i) jie[i] = i*jie[i-1];
	int T; scanf("%d\n",&T);
	while (T--)
	{
		scanf("%s %d\n",s+1,&m); n = strlen(s+1);
		dp(); printf("%d\n",f[(1<<n)-1][0]);
	}
	fclose(stdin); fclose(stdout);
	return 0;
}
posted @ 2015-02-25 15:21  lmxyy  阅读(193)  评论(0编辑  收藏  举报