您的位置:首页 > 其它

[BZOJ1004][HNOI2008]Cards(Burnside引理+dp)

2018-03-08 14:01 337 查看
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1004

让我们先来分析一下题目,题目中有这样几句话:

1、两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗成另一种

2、输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

ok让我们来看一下Burnside引理的定义

设G={a1,a2,…ag}是目标集[1,n]上的置换群。每个置换都写成不相交循环的乘积。

是在置换ak的作用下不动点的个数,也就是长度为1的循环的个数。通过上述置换的变换操作后可以相等的元素属于同一个等价类。若G将[1,n]划分成l个等价类,则:

等价类个数为:



(来自百度百科)

我们发现,burnside引理中的

循环节“(循环节指一个颜色经过k次染色之后又可以变回来,k就是循环节的长度)

等价类“(等价类指本质上相等的着色方案即’等价‘)

刚好何我们一开始提到的那两句话一样,那么我们就可以用burnside引理来解决这道题啦!

让我们在通俗一点的解释burnside引理:

有G个置换,对于第i种置换,我们有循环节为1的循环c(i)个,那么不重复的着色方案数就是

∑c(i)G∑c(i)G

因为答案在mod意义下并且mod是质数,所以除以G要变成逆元

∑c(i)∗inv(G)∑c(i)∗inv(G)

现在的问题就是找c(i),那么我们用dp来找。如果要使循环节为1,那么我们只需要把互相影响的点染成同一个颜色即可,记录一个循环节的大小size然后三维背包即可。

因为这题数据非常小,所以我们可以用O(m2∗sr∗sg∗sb)O(m2∗sr∗sg∗sb)的方法来做,但这只是数据很小的请假,如果数据在大一些,我们就需要用到polya定理了。

(待更新)

code:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=110;
ll powmod(int a,int b,int p)
{
ll ans=1;
while(b!=0)
{
if(b&1) ans=(ans*a)%p;
a=(a*a)%p;
b>>=1;
}
return ans;
}
ll inv(int a,int p){return powmod(a,p-2,p);}//费马小定理计算逆元
int sr,sb,sg;
int n,m;
ll mod;
int ch[maxn][maxn],size[maxn];//ch置换群,size循环节大小
ll f[maxn][maxn][maxn];//背包染色,互相可能影响的点染成同种颜色 的方案数
bool vis[maxn];//循环节用判断
ll dp(int x)//当前是第x个置换
{
int cnt=0;
memset(vis,false,sizeof(vis));
memset(size,0,sizeof(size));
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
cnt++;
int p=ch[x][i];
while(!vis[p]){vis[p]=true;size[cnt]++;p=ch[x][p];}
}
memset(f,0,sizeof(f));
f[0][0][0]=1;
for(int i=1;i<=cnt;i++)
for(int j=sr;j>=0;j--)
for(int k=sb;k>=0;k--)
for(int l=sg;l>=0;l--)
{
if(j>=size[i]) f[j][k][l]=(f[j][k][l]+f[j-size[i]][k][l])%mod;
if(k>=size[i]) f[j][k][l]=(f[j][k][l]+f[j][k-size[i]][l])%mod;
if(l>=size[i]) f[j][k][l]=(f[j][k][l]+f[j][k][l-size[i]])%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",&ch[i][j]);
m++;//注意不动也是一个置换
for(int i=1;i<=n;i++) ch[m][i]=i;
ll ans=0;
for(int i=1;i<=m;i++) ans=(ans+dp(i))%mod;
ans=(ans*inv(m,mod))%mod;
printf("%lld\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: