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; }