[bzoj1004][HNOI2008]Cards
[bzoj1004][HNOI2008]Cards
标签: 置换 Burnside引理
扯淡
题目中说了这样一句话
两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)
怎么样?是不是很棘手。然而
输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。
题意
n张牌,3种颜色,给你一个置换集合G(且保证对于任意\(f,g\in G,fg \in G\)),让你对每张牌染色,使得红色Sred种,蓝色Sblue种,绿色Sgreen种。求不同的染色方案总数。
题解
根据burnside引理:等价类的个数等于每一个置换的不动点的个数之和的平均数。
如果是一个不动点的话,那么这个置换每一个循环中的元素颜色都一样。
这样我们就能跑一个三维背包了。
Code
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<vector>
using namespace std;
#define ll long long
#define REP(i,a,b) for(int i=(a),_end_=(b);i<=_end_;i++)
#define DREP(i,a,b) for(int i=(a),_end_=(b);i>=_end_;i--)
#define EREP(i,a) for(int i=start[(a)];i;i=e[i].next)
inline int read()
{
int sum=0,p=1;char ch=getchar();
while(!(('0'<=ch && ch<='9') || ch=='-'))ch=getchar();
if(ch=='-')p=-1,ch=getchar();
while('0'<=ch && ch<='9')sum=sum*10+ch-48,ch=getchar();
return sum*p;
}
const int maxn=100;
int mod;
int dp[maxn][maxn][maxn],sr,sb,sg,n,m,ans;
int fa[maxn*3],w[maxn*3],cnt;
int fin(int x)
{
return x==fa[x]?x:fa[x]=fin(fa[x]);
}
int power(int a,int b)
{
int ans=1;
while(b)
{
if(b & 1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
void init()
{
sr=read();sb=read();sg=read();n=sr+sb+sg;
m=read();mod=read();
REP(i,1,m+1)
{
REP(j,1,n)fa[j]=j;
if(i<m+1)
{
REP(j,1,n)
{
int v=read();
int x=fin(j),y=fin(v);
if(x!=y)
{
fa[y]=x;
}
}
}
memset(w,0,sizeof(w));
cnt=0;
REP(col,1,n)
{
int flag=1;
REP(j,1,n)
{
if(fin(j)==col)
{
if(flag)flag=0,cnt++;
w[cnt]++;
}
}
}
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
REP(j,1,cnt)
{
DREP(x,sr,0)
{
DREP(y,sb,0)
{
DREP(z,sg,0)
{
if(x>=w[j])dp[x][y][z]+=dp[x-w[j]][y][z];
if(y>=w[j])dp[x][y][z]+=dp[x][y-w[j]][z];
if(z>=w[j])dp[x][y][z]+=dp[x][y][z-w[j]];
dp[x][y][z]%=mod;
}
}
}
}
ans=(ans+dp[sr][sb][sg])%mod;
}
cout<<ans*power(m+1,mod-2)%mod<<endl;
}
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
init();
return 0;
}