【BZOJ1004】Cards(HNOI2008)-Burnside引理+DP+逆元

测试地址:Cards
做法:本题需要用到Burnside引理+DP+逆元。
注意到,按照题目中给的定义,给出的m个置换加上回到自身的置换合在一起是一个大小为m+1的置换群,那么我们可以DP求出对于每个置换的等价类数目,然后使用Burnside引理求出最后答案。至于怎么DP,只要先求出每个置换中各个循环内的元素的个数,然后就可以使用O(n4)的三维背包求出了(这里n的最大值视为20,因为每种牌最多只有20张)。最后要乘上一个1/(m+1),转化成乘上m+1p的逆元即可。总的时间复杂度为O(mn4)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int a,b,c,m,p,n,x[110];
int f[65][25][25][25],g[65],ans=0;
bool vis[65];

int power(int a,int b)
{
    int s=1,ss=a;
    while(b)
    {
        if (b&1) s=(s*ss)%p;
        b>>=1;ss=(ss*ss)%p;
    }
    return s;
}

void work()
{
    int tot=0;
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        if (!vis[i])
        {
            g[++tot]=0;
            int now=i;
            while(!vis[now]) vis[now]=1,g[tot]++,now=x[now];
        }
    memset(f,0,sizeof(f));
    f[0][0][0][0]=1;
    for(int i=1;i<=tot;i++)
    {
        for(int sa=0;sa<=a;sa++)
            for(int sb=0;sb<=b;sb++)
                for(int sc=0;sc<=c;sc++)
                {
                    if (sa>=g[i]) f[i][sa][sb][sc]=(f[i][sa][sb][sc]+f[i-1][sa-g[i]][sb][sc])%p;
                    if (sb>=g[i]) f[i][sa][sb][sc]=(f[i][sa][sb][sc]+f[i-1][sa][sb-g[i]][sc])%p;
                    if (sc>=g[i]) f[i][sa][sb][sc]=(f[i][sa][sb][sc]+f[i-1][sa][sb][sc-g[i]])%p;
                }
    }
    ans=(ans+f[tot][a][b][c])%p;
}

int main()
{
    scanf("%d%d%d%d%d",&a,&b,&c,&m,&p);
    n=a+b+c;
    for(int i=1;i<=n;i++) x[i]=i;
    work();
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++) scanf("%d",&x[j]);
        work();
    }
    ans=(power(m+1,p-2)*ans)%p;
    printf("%d",ans);

    return 0;
}
posted @ 2017-11-25 17:44  Maxwei_wzj  阅读(93)  评论(0编辑  收藏  举报