[BZOJ1004] [HNOI2008] Cards (Polya定理)

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<p<100)。n=Sr+Sb+Sg。
接下来 m 行,每行描述一种洗牌法,每行有 n 个用空格隔开的整数 X1X2...Xn,恰为 1 到 n 的一个排列,
表示使用这种洗牌法,第 i位变为原来的 Xi位的牌。输入数据保证任意多次洗牌都可用这 m种洗牌法中的一种代
替,且对每种洗牌法,都存在一种洗牌法使得能回到原状态。

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。

Source

Solution

  $Polya$定理($Burnside$引理)戳这

  其作用就是求出有几种本质不同的染色方案

  由于每种颜色的个数有限制,需要用三维的$0/1$背包求出每一种置换下不动点方案数(注意不洗牌也是一种置换)

  呃,需要用乘法逆元,否则你会死得很难看

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int p, x[65][65], f[25][25][25], siz[65];
 4 bool vis[65];
 5 
 6 int qpow(int a, int b)
 7 {
 8     int ans = 1;
 9     for(; b; b >>= 1, a = a * a % p)
10         if(b & 1) ans = ans * a % p;
11     return ans;
12 }
13 
14 int main()
15 {
16     int sr, sg, sb, m, inv, ans = 0, tot;
17     cin >> sr >> sb >> sg >> m >> p;
18     for(int i = 1; i <= m; ++i)
19         for(int j = 1; j <= sr + sg + sb; ++j)
20             cin >> x[i][j];
21     for(int i = 1; i <= sr + sg + sb; ++i)
22         x[m + 1][i] = i;
23     for(int z = 1; z <= m + 1; ++z)
24     {
25         memset(f, 0, sizeof(f));
26         memset(vis, 0, sizeof(vis));
27         memset(siz, 0, sizeof(siz));
28         f[0][0][0] = 1, tot = 0;
29         for(int i = 1; i <= sr + sg + sb; ++i)
30             if(!vis[i])
31             {
32                 ++tot;
33                 for(int j = i; !vis[j]; j = x[z][j])
34                     vis[j] = true, ++siz[tot];
35             }
36         for(int l = 1; l <= tot; ++l)
37             for(int i = sr; ~i; --i)
38                 for(int j = sg; ~j; --j)
39                     for(int k = sb; ~k; --k)
40                     {
41                         if(i >= siz[l])
42                             f[i][j][k] = (f[i][j][k] + f[i - siz[l]][j][k]) % p;
43                         if(j >= siz[l])
44                             f[i][j][k] = (f[i][j][k] + f[i][j - siz[l]][k]) % p;
45                         if(k >= siz[l])
46                             f[i][j][k] = (f[i][j][k] + f[i][j][k - siz[l]]) % p;
47                     }
48         ans = (ans + f[sr][sg][sb]) % p;
49     }
50     cout << ans * qpow(m + 1, p - 2) % p << endl;
51     return 0;
52 }
View Code

 

转载于:https://www.cnblogs.com/CtrlCV/p/5618754.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值