用到了置换群Burnside引理:
等价类数目 = average( C(fi) ),其中C(fi):对于置换fi的"不动点"的数目(不动点:若将所有元素染色,经fi置换后颜色不变的一组方案)
【题解】
对于每个fi求不动点数目,用dp:
F[i][j][k]表示:前i个循环中,红色有j个,蓝色有k个的方案数
状态转移:讨论第i个循环涂什么颜色就行了
注意:"不洗牌"也是一种情况对应置换(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;
}