首页 > 代码库 > bzoj1072: [SCOI2007]排列perm

bzoj1072: [SCOI2007]排列perm

思路:状压dp,f[i][j]表示新的排列的状态为i(也就是新的排列已经选了哪些数),然后模d的余数为j的方案数。

但考虑到可能有些数会出现多次,假设一个数x出现了cnt[x]次,那么对于一个可行的答案,显然也包含cnt[x]个x,那么这样的答案就会被计算多次,因为如果状态i先加入第一个x再加入第二个x和状态i先加入第二个x再加入第一个x显然是算作两种不同的方案,但其实他们是一样的,但对于某一个确定的合法的答案,x的位置不会变,因此如果重复的情况只可能是该答案中所有的x在互换,这种互换会被多次统计但答案其实只有一个,可以发现这种互换的情况数有cnt[x]!种(本质上就是一个长为cnt[x]的排列的情况数),然后答案除以cnt[x]!就可以完成去重了。

技术分享
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 1<<11
 
int cases,p;
int cnt[15],f[maxn][maxn];
char s[15];
 
int main(){
    scanf("%d",&cases);
    while (cases--){
        scanf("%s%d",s,&p);int n=strlen(s);
        memset(f,0,sizeof(f)),f[0][0]=1,memset(cnt,0,sizeof(cnt));
        for (int i=0;i<n;i++) cnt[s[i]-0]++;
        for (int i=0;i<(1<<n);i++)
            for (int m=0;m<p;m++)
                for (int j=0;j<n;j++)
                    if (!(i&(1<<j))) f[i|(1<<j)][(m*10+s[j]-0)%p]+=f[i][m];
        int ans=f[(1<<n)-1][0];
        for (int i=0;i<=9;i++)
            for (int j=2;j<=cnt[i];j++)
                ans/=j;
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

bzoj1072: [SCOI2007]排列perm