bzoj1004: [HNOI2008]Cards(burnside引理+DP)

  题目大意:3种颜色,每种染si个,有m个置换,求所有本质不同的染色方案数。

  置换群的burnside引理,还有个Pólya过几天再看看。。。

  burnside引理:有m个置换k种颜色,所有本质不同的染色方案数就是每种置换的不变元素的个数的平均数。

  求每种置换的不变元素的个数用背包解决。因为置换之后元素不变,所以对于每个循环节我们要染一个颜色,于是先处理出循环节作为背包中的“物体”,然后一个三维背包解决。f[i][j][k]的i j k表示三种颜色分别还可以染多少次。

  除m%p用费马小定理就行了,我才不用exGCD...(QAQ因为老是忘记怎么写,快速幂多资磁

  没清零WA了2次。。。最近老是出小问题

  UPD:去看了一波polya定理,例题poj2409中,一开始我不理解为什么旋转1次和旋转2次要当做2个置换,看了群的概念才知道呜呜呜....  

  封闭性就是指连续运算得到的结果也在群里面,所以旋转1次和旋转2次要当做两个置换。

  但是回到这题,为什么不用将任意一个置换再生成新的置换再计算呢?原来...

  

  “输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代替”就满足了置换群的定义,多次洗牌->连续运算,可用一种代替 说明这连续运算的结果也算是一种置换。

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
void read(ll &k)
{
    k=0;int f=1;char c=getchar();
    while(c<'0'||c>'9')c=='-'&&(f=-1),c=getchar();
    while(c<='9'&&c>='0')k=k*10+c-'0',c=getchar();
    k*=f;
}
ll sr,sb,sg,m,p,n,ans;
ll next[110],a[70][110],d[110],f[2][70][70][70];
ll dp(int x)
{
    int cnt=0;
    for(int i=1;i<=n;i++)next[i]=0;
    for(int i=1;i<=n;i++)
    if(!next[i])
    {
        d[++cnt]=next[i]=1;
        int p=i;
        while(!next[a[x][p]])
        {
            p=a[x][p];
            next[p]=1;
            d[cnt]++;
        }
    }
    for(int i=0;i<=sr;i++)
    for(int j=0;j<=sb;j++)
    for(int k=0;k<=sg;k++)
    f[1][i][j][k]=f[0][i][j][k]=0;
    f[1][0][0][0]=1;
    int now=0;
    for(int l=1;l<=cnt;l++)
    {
        for(int i=0;i<=sr;i++)
        for(int j=0;j<=sb;j++)
        for(int k=0;k<=sg;k++)
        {
            if(i>=d[l])f[now][i][j][k]=(f[now^1][i-d[l]][j][k]+f[now][i][j][k])%p;
            if(j>=d[l])f[now][i][j][k]=(f[now^1][i][j-d[l]][k]+f[now][i][j][k])%p;
            if(k>=d[l])f[now][i][j][k]=(f[now^1][i][j][k-d[l]]+f[now][i][j][k])%p;
        }
        now^=1;
    }
    return f[now^1][sr][sb][sg];
}
ll mi(ll a,int b)
{
    ll t=1,y=a;
    while(b)
    {
        if(b&1)t=(t*y)%p;
        y=(y*y)%p;
        b>>=1;
    }
    return t%p;
}
int main()
{
    read(sr);read(sb);read(sg);read(m);read(p);n=sr+sb+sg;
    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++)
    read(a[i][j]);
    m++;
    for(int i=1;i<=n;i++)a[m][i]=i;
    for(int i=1;i<=m;i++)
    ans=(ans+dp(i))%p;
    printf("%lld
",ans*mi(m,p-2)%p);
}
View Code
原文地址:https://www.cnblogs.com/Sakits/p/6959727.html