bzoj 1004
计数好题
首先看到这种问题直接想到的应该是polya定理
可是对颜色使用个数有限制啊!
没关系,我们分析一下polya定理的表达式:
$\frac{1}{|G|}\sum_{i=1}^{n}m^{c_{i}}$
可以看到,这其中的每一项等价于用$m$种颜色对每个循环节任意染色的方案数(即对每个循环节内的元素染成相同颜色的方案数)
因此对于这个有限制的问题,我们可以用简单的背包求出这一方案数
最后对方案数求和,乘个逆元即可
注意:不要忘了群的基本定义,一个置换群里是需要单位元的,因此需要补充一个原始置换
代码:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long using namespace std; ll sr,sb,sg,m,p; int to[65]; bool vis[65]; int siz[65][65]; ll f[65][25][25][25]; int val[65]; ll pow_mul(ll x,ll y) { ll ret=1; while(y) { if(y&1)ret=ret*x%p; x=x*x%p,y>>=1; } return ret; } int main() { scanf("%lld%lld%lld%lld%lld",&sr,&sb,&sg,&m,&p); ll n=sr+sb+sg; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++)scanf("%d",&to[j]); memset(vis,0,sizeof(vis)); for(int j=1;j<=n;j++) { int p=j; if(vis[p])continue; val[i]++; while(!vis[p]) { siz[i][val[i]]++; vis[p]=1; p=to[p]; } } } m++; val[m]=n; for(int i=1;i<=n;i++)siz[m][i]=1; ll s=0; for(int t=1;t<=m;t++)//枚举第几个循环 { ll S=0; memset(f,0,sizeof(f)); f[0][0][0][0]=1; for(int i=1;i<=val[t];i++) { S+=siz[t][i]; for(int j=0;j<=sr;j++)//枚举红色的个数 { for(int k=0;k<=sb;k++) { if(S-j-k>sg)continue; if(j>=siz[t][i])f[i][j][k][S-j-k]+=f[i-1][j-siz[t][i]][k][S-j-k]; if(k>=siz[t][i])f[i][j][k][S-j-k]+=f[i-1][j][k-siz[t][i]][S-j-k]; if(S-j-k>=siz[t][i])f[i][j][k][S-j-k]+=f[i-1][j][k][S-j-k-siz[t][i]]; f[i][j][k][S-j-k]%=p; } } } s+=f[val[t]][sr][sb][sg]; s%=p; } s=s*pow_mul(m,p-2)%p; printf("%lld\n",s); return 0; }