P1446 [HNOI2008]Cards [Burnside引理]

C a r d s Cards Cards


D e s c r i p t i o n \mathcal{Description} Description
小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有多少种染色方案,Sun很快就给出了答案.

进一步,小春要求染出Sr张红色,Sb张蓝色,Sg张绿色.他又询问有多少种方案,Sun想了一下,又给出了正确答案. 最后小春发明了M种不同的洗牌法,这里他又问Sun有多少种不同的染色方案.两种染色方法相同当且仅当其中一种可以通过任意的洗牌法(即可以使用多种洗牌法,而每种方法可以使用多次)洗成另一种.

Sun发现这个问题有点难度,决定交给你,答案可能很大,只要求出答案除以P的余数(P为质数).

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


S o l u t i o n \mathcal{Solution} Solution

加粗字体保证了多次洗牌与使用一次其中一种洗牌方式的结果相同,
相当于给出的置换总数仍是 M M M
.

对于 M M M置换, 根据 B u r n s i d e 引 理 Burnside引理 Burnside,
我们只需将 M M M 个置换下 状态始终不变的方案数 n u m i num_i numi 叠加,
再加上 题目没有给出的 单位置换 的方案数, 除 M + 1 M+1 M+1 即可得到答案.


n u m i num_i numi 可以使用 d p dp dp 求解,
在第 i i i 个置换下, 含有 s i z e size size个循环节, 每个循环节长度为 l e n i len_i leni,

每个循环节内都必须染上同一个颜色才可以循环.
要求解的就是 三个不同的背包中 装 s i z e size size 个物品 .

F [ i , j , k ] F[i,j,k] F[i,j,k] 表示三种颜色分别使用 i , j , k i,j,k i,j,k 个的方案数,
F [ i , j , k ] = F [ i − l e n i , j , k ] + F [ i , j − l e n i , k ] + F [ i , j , k − l e n i ] F[i,j,k]=F[i-len_i,j,k]+F[i,j-len_i,k]+F[i,j,k-len_i] F[i,j,k]=F[ileni,j,k]+F[i,jleni,k]+F[i,j,kleni],
最后 F [ S r , S b , S g ] F[Sr,Sb,Sg] F[Sr,Sb,Sg] 即为该置换方案数.


C o d e \mathcal{Code} Code

#include<cstdio>
#include<cstring>
#define reg register

int Sr;
int Sb;
int Sg;
int M;
int N;
int P;
int mod;
int Ans;
int Len[75];
int len[75];
int Zh[75][75];
int F[75][75][75];

bool Used[75];

int gcd(int a, int b){ return !b?a:gcd(b, a%b); }

int Find_circle(int x){
        memset(Used, 0, sizeof Used);
        int s = 0;
        for(reg int i = 1; i <= N; i ++)
                if(!Used[i]){
                        Used[i] = 1;
                        len[++ s] = 1;
                        int to = Zh[x][i];
                        while(!Used[to]) Used[to] = 1, to = Zh[x][to], len[s] ++;
                }
        return s;
}

void Work(int size){
        memset(F, 0, sizeof F);
        F[0][0][0] = 1;
        for(reg int p = 1; p <= size; p ++)
                for(reg int i = Sr; ~i; i --)
                        for(reg int j = Sb; ~j; j --)
                                for(reg int k = Sg; ~k; k --){
                                        int &t = F[i][j][k];
                                        if(i >= len[p]) t = (1ll*t + F[i-len[p]][j][k]) % mod;
                                        if(j >= len[p]) t = (1ll*t + F[i][j-len[p]][k]) % mod;
                                        if(k >= len[p]) t = (1ll*t + F[i][j][k-len[p]]) % mod;
                                }
        Ans = (1ll*Ans + F[Sr][Sb][Sg]);
}

int KSM(int a, int b){
        a %= mod;
        int s = 1;
        while(b){
                if(b & 1) s = 1ll*s*a % mod;
                a = 1ll*a*a % mod;
                b >>= 1;
        }
        return s;
}

int main(){
        scanf("%d%d%d%d%d", &Sr, &Sb, &Sg, &M, &P);
        mod = P;
        N = Sr + Sb + Sg;
        for(reg int i = 1; i <= M; i ++)
                for(reg int j = 1; j <= N; j ++) scanf("%d", &Zh[i][j]);
        for(reg int i = 1; i <= M; i ++) Work(Find_circle(i));
        for(reg int i = 1; i <= N; i ++) len[i] = 1;
        Work(N);
        Ans = (1ll*Ans*KSM(M+1, mod-2)) % mod;
        printf("%d\n", Ans);
        return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值