bzoj1004: [HNOI2008]Cards

题目链接

bzoj1004

题意

Description

小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有多少种染色方案,Sun很快就给出了答案.进一步,小春要求染出Sr张红色,Sb张蓝色,Sg张绝色.他又询问有多少种方案,Sun想了一下,又给出了正确答案. 最后小春发明了M种不同的洗牌法,这里他又问Sun有多少种不同的染色方案.两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗成另一种.Sun发现这个问题有点难度,决定交给你,答案可能很大,只要求出答案除以P的余数(P为质数).

Input

第一行输入 5 个整数:Sr,Sb,Sg,m,p(m<=60,m+1

Output

不同染法除以P的余数

Sample Input

1 1 1 2 7
2 3 1
3 1 2

Sample Output

2

HINT

有2 种本质上不同的染色法RGB 和RBG,使用洗牌法231 一次可得GBR 和BGR,使用洗牌法312 一次 可得BRG 和GRB。

100%数据满足 Max{Sr,Sb,Sg}<=20。

题解

Burnside定理:有m个置换k种颜色,所有本质不同的染色方案数就是每种置换的不变的染色方案的个数的平均数。
对于一个置换找出所有循环节,将循环节染成相同颜色就是一种不变的染色方案。有颜色个数的限制就用背包dp。
要算平均数就又用乘法逆元,这题可以用扩展欧几里得,也可以用费马小定理。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
int sa,sb,sc,m,p,n,a[70][70],f[70][70][70],s[70],v[70],ans;
int dp(int x){
    memset(v,0,sizeof(v));
    memset(s,0,sizeof(s));
    memset(f,0,sizeof(f));
    int cnt=0;
    for(int i=1;i<=n;i++)
    if(!v[i]){
        v[i]=1; s[++cnt]=1;
        for(int p=i;!v[a[x][p]];p=a[x][p]) s[cnt]++,v[a[x][p]]=1;
    }
    f[0][0][0]=1;
    for(int i=1;i<=cnt;i++)
     for(int j=sa;j>=0;j--)
      for(int k=sb;k>=0;k--)
       for(int l=sc;l>=0;l--){
            if(s[i]<=j) f[j][k][l]=(f[j][k][l]+f[j-s[i]][k][l])%p;
            if(s[i]<=k) f[j][k][l]=(f[j][k][l]+f[j][k-s[i]][l])%p;
            if(s[i]<=l) f[j][k][l]=(f[j][k][l]+f[j][k][l-s[i]])%p;
       }
    return f[sa][sb][sc];
}
int power(int x,int y){
    int tmp=1;
    for(;y;y>>=1,x=x*x%p)
    if(y&1) tmp=tmp*x%p;
    return tmp;
}
int main(){
    scanf("%d%d%d%d%d",&sa,&sb,&sc,&m,&p);
    n=sa+sb+sc;
    for(int i=1;i<=m;i++)
     for(int j=1;j<=n;j++)
     scanf("%d",&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("%d",ans*power(m,p-2)%p);
    return 0;
}

你可能感兴趣的:(bzoj1004: [HNOI2008]Cards)