[HNOI2008]Cards(burnside引理+背包)

题意

有三种颜色分别(a,b,c)个,用它们给(a+b+c)的序列染色,求染色方案数,同时给定(m)个置换,两种方案相同当且仅当存在一个置换使得其中一种方案变成另一种。数据保证任意一种置换排列方式都可以只用一个置换替代,且对于任一个置换,都存在另一个置换是它的逆元

Sol

(Burnside)引理:对于一个置换群,用(c(a_i))表示在置换(a_i)下不变的元素个数,那么本质不同的方案数为:

(L=frac{Sigma_{i=1}^{|G|}c(a_i)}{|G|})

其中(|G|)表示群的大小

根据原题的限制,可以看出这些置换再加上一个单位元即可满足群的定义,构成一个置换群

对于每一个置换(a_i),我们想要知道有多少种染色方案使得这种方案在(a_i)下不变

定义:(n阶循环)((a_1,a_2,...,a_n)=egin{pmatrix} a_1 & a_2... & a_n \ a_2 & a_3... & a_1 end{pmatrix})

(循环可以看做一个首尾顺次连接的环,反正我是这么看的

容易发现如果对一个循环进行染色且使得置换前后颜色不变,必须将它们染成同一种颜色

任一个置换可以写成几个互不相交的循环的乘积,其中互不相交指的是两个循环间没有公共位置(a_i)

举个栗子:

(egin{pmatrix} 1 & 2 & 3 & 4 & 5\ 3 & 5 & 1 & 4 & 2 end{pmatrix}=(13)(25)(4))

那么将置换(a_i)分成(k)个循环,满足条件的方案(j)应该满足它的每一个循环位置上的颜色是一样的。用类似求环的方法求出所有循环,用(dp[h][f][u])表示当前三种颜色各已经用了(h,f,u)个的满足条件的方案数,以一整个循环为一个单位当作背包问题进行处理,(c(a_i)=f[a][b][c]),最后带入公式即可

注意:前面加入的单位元也要算作一个置换,所以总共应该有(m+1)个置换

Code:

#include<bits/stdc++.h>
#define N 70
using namespace std;
int a[4],n,m,mod,ans;
int f[22][22][22];
int t[N],size[N],tot;
bool vis[N];

template <class T>
void read(T &x)
{
	char c;int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign;
}
int quickpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=ret*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ret;
}
int calc()
{
	tot=0;
	memset(vis,0,sizeof(vis));
	memset(f,0,sizeof(f));
	f[0][0][0]=1;
	//找循环
	for(int i=1;i<=n;++i)
	{
		if(!vis[i])
		{
			size[++tot]=1;
			vis[i]=1;
			int now=t[i];
			while(now!=i) vis[now]=1,size[tot]++,now=t[now];
		}
	}
	for(int i=1;i<=tot;++i)
	{
		for(int b=a[1];b>=0;--b)
		{
			for(int c=a[2];c>=0;--c)
			{
				for(int d=a[3];d>=0;--d)
				{
					if(b>=size[i]) f[b][c][d]=(f[b][c][d]+f[b-size[i]][c][d])%mod;
					if(c>=size[i]) f[b][c][d]=(f[b][c][d]+f[b][c-size[i]][d])%mod;
					if(d>=size[i]) f[b][c][d]=(f[b][c][d]+f[b][c][d-size[i]])%mod;
				}
			}
		}
	}
	return f[a[1]][a[2]][a[3]]%mod;
}
int main()
{
	read(a[1]);read(a[2]);read(a[3]);
	n=a[1]+a[2]+a[3];
	read(m);read(mod);
	for(int i=1;i<=m;++i)
	{
		for(int j=1;j<=n;++j) read(t[j]);
		ans=(ans+calc())%mod;
	}
	for(int i=1;i<=n;++i) t[i]=i;//单位置换 
	ans=(ans+calc())%mod;
	cout<<ans*quickpow(m+1,mod-2)%mod<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11420774.html