【BZOJ1072】【SCOI2007】排列 [状压DP]
排列
Time Limit: 10 Sec Memory Limit: 128 MB[Submit][Status][Discuss]
Description
给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0)。
例如123434有90种排列能被2整除,其中末位为2的有30种,末位为4的有60种。
Input
输入第一行是一个整数T,表示测试数据的个数,以下每行一组s和d,中间用空格隔开。
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
3
3628800
90
3
6
1398
HINT
s的长度不超过10, 1<=d<=1000, 1<=T<=15
Solution
我们运用状压DP,令 f[j][opt] 表示当前余数为 j,状态为opt的方案。
状态记录的是:各个数字被用了几次。
那么我们就可以状压了。先DFS出每个状态,记sum[k]表示后缀积,那么显然 从 opt 转移到 第k个数字多用一次的状态 就是 opt + sum[k + 1]。
Code
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
typedef long long s64;
const int ONE = 20005;
int T, n, m;
int num;
int vis[ONE], Num[20], sum[20];
int f[1005][20005];
int Sta[ONE][10];
char ch[ONE];
int get()
{
int res=1,Q=1;char c;
while( (c=getchar())<48 || c>57 )
if(c=='-')Q=-1;
res=c-48;
while( (c=getchar())>=48 && c<=57 )
res=res*10+c-48;
return res*Q;
}
void Dfs(int T)
{
if(T > 10)
{
num++;
for(int i = 0; i <= 9; i++)
Sta[num][i] = vis[i];
return;
}
for(int i = 0; i <= Num[T]; i++)
vis[T] = i, Dfs(T + 1);
}
void Deal()
{
memset(f, 0, sizeof(f));
memset(Num, 0, sizeof(Num));
memset(sum, 0, sizeof(sum));
num = 0;
scanf("%s", ch + 1); m = get();
n = strlen(ch + 1);
for(int i = 1; i <= n; i++) Num[ch[i] - '0']++;
sum[10] = 1; for(int i = 9; i >= 0; i--) sum[i] = sum[i + 1] * (Num[i] + 1);
Dfs(0);
f[0][1] = 1;
for(int opt = 1; opt <= num; opt++)
for(int j = 0; j < m; j++)
if(f[j][opt])
for(int k = 0; k <= 9; k++)
{
if(Sta[opt][k] >= Num[k]) continue;
int to = opt + sum[k + 1];
f[(j * 10 + k) % m][to] += f[j][opt];
}
printf("%d\n", f[0][num]);
}
int main()
{
T = get();
while(T--)
Deal();
}