用到了置换群Burnside引理:
等价类数目 = average( C(fi) ),其中C(fi):对于置换fi的"不动点"的数目(不动点:若将所有元素染色,经fi置换后颜色不变的一组方案)
【题解】
注意:"不洗牌"也是一种情况对应置换(1 2 …n),因此置换共有m+1个
具体流程我在代码注释中已经写得很清楚了
#include<stdio.h> #include<stdlib.h> int cha[100]={0},f[100][100][100]={0},vis[100]={0},xh[100]={0};//cha[]:洗牌方式, xh[]:cha[]分解成的循环节长度 int mod; void gcd(int a,int b,int& d,int& x,int& y) { int t; if(b==0) { d=a; x=1; y=0; return; } gcd(b,a%b,d,x,y); t=x; x=y; y=t-a/b*y; } int ni(int a,int n) { int d,x,y; gcd(a,n,d,x,y); return (x+n)%n; } int main() { int Sr,Sb,Sg,n,m,i,j,k,l,p,ans=0;//p代表xh[]的位数,不是mod! scanf("%d%d%d%d%d",&Sr,&Sb,&Sg,&m,&mod); m++;//"不洗牌"也算作一种方案 n=Sr+Sb+Sg; for(l=1;l<=m;l++) { for(i=1;i<=n;i++) { if(l==1) cha[i]=i; else scanf("%d",&cha[i]); vis[i]=0; } p=0;//分解为循环节 for(i=1;i<=n;i++) if(vis[i]==0) { j=i; xh[++p]=0; do { vis[j]=1; xh[p]++; j=cha[j]; } while(j!=i); }//接下来统计不动点的个数: for(i=0;i<=p;i++) for(j=0;j<=Sr;j++) for(k=0;k<=Sb;k++) f[i][j][k]=0; f[0][0][0]=1;//三维背包的边界 for(i=1;i<=p;i++) for(j=0;j<=Sr;j++) for(k=0;k<=Sb;k++) { if(j+k>i) break; if(i-j-k>Sg) continue; if(j>=xh[i]) f[i][j][k]+=f[i-1][j-xh[i]][k]; if(k>=xh[i]) f[i][j][k]+=f[i-1][j][k-xh[i]]; if(i-j-k>=xh[i]) f[i][j][k]+=f[i-1][j][k]; f[i][j][k]%=mod; } ans+=f[p][Sr][Sb]; } ans=(ans*ni(m,mod))%mod; printf("%d",ans); return 0; }