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

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1004


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

1、两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗成另一种
2、输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

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

设G={a1,a2,…ag}是目标集[1,n]上的置换群。每个置换都写成不相交循环的乘积。
c1(ak) c 1 ( a k ) 是在置换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 ) ∗ i n v ( G )

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

因为这题数据非常小,所以我们可以用 O(m2srsgsb) O ( m 2 ∗ s r ∗ s g ∗ s b ) 的方法来做,但这只是数据很小的情况,如果数据再大一些,我们就需要用到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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值