【bzoj1004】[HNOI2008]Cards burnside引理+dp

首先,加入一个置换,每个数i对应自身
计算方案数%P
ans=∑D(i)/M (1<=i<=M)
D(i)表示在置换i下不变的染色数
考虑如何求D(i)
对于置换i,求出它的循环节,每个循环节必须染成同一个颜色,但是对总数有限制
f[i][j][k]表示用i个红色,j个蓝色,k个绿色的方案数
把每个循环节视为一个物品,权值为长度。
f[i][j][k]=f[i-d[p]][j][k]+f[i][j-d[p]][k]+f[i][j][k-d[p]] 
d[p]表示第p个循环节的长度

注意枚举顺序,p要在最外面,保证每个物品只取一次


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream> 

using namespace std;

int f[50][50][50];
int a[100],b[100];
bool vis[100];
int n,m,ans,mod,sr,sb,sg;

int power(int x,int y)
{
	int ans=1;
	while (y)
	{
		if (y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}

int solve()
{
	memset(f,0,sizeof(f));
	memset(vis,0,sizeof(vis));
	int tot=0;
	for (int i=1;i<=n;i++)
	  if (!vis[i])
	  {
	  	tot++;b[tot]=1;vis[i]=1;
	  	int j=i;
	  	while (!vis[a[j]]) j=a[j],vis[j]=1,b[tot]++;
	  }
	f[0][0][0]=1;
	for (int p=1;p<=tot;p++)
	  for (int i=sr;i>=0;i--)
	    for (int j=sb;j>=0;j--)
	      for (int k=sg;k>=0;k--)
	      {
	      	if (i>=b[p]) f[i][j][k]+=f[i-b[p]][j][k];
	      	if (j>=b[p]) f[i][j][k]+=f[i][j-b[p]][k];
	      	if (k>=b[p]) f[i][j][k]+=f[i][j][k-b[p]];
	      	f[i][j][k]%=mod;
	      }
	return f[sr][sb][sg];
}

int main()
{
	scanf("%d%d%d%d%d",&sr,&sb,&sg,&m,&mod);
	n=sr+sb+sg;
	for (int i=1;i<=m;i++)
	{
		for (int j=1;j<=n;j++) scanf("%d",&a[j]);
		ans+=solve();ans%=mod;
	}
	m++;
	for (int i=1;i<=n;i++) a[i]=i;
	ans+=solve();ans%=mod;
	ans=ans*power(m,mod-2)%mod;
	printf("%d\n",ans);
	return 0;
}


你可能感兴趣的:(【bzoj1004】[HNOI2008]Cards burnside引理+dp)