[BZOJ] 1004 [HNOI2008]Cards
题意
有\(r,b,g\)三种卡牌,各有各的张数,卡牌排列的不同方案数。
两种方案相同当且仅当一种方案经过\(m\)个置换的其中某种置换可以变成另一种。
保证\(m\)个个置换任意组合都可由其中一个代替,并且保证对于每一种置换都存在另外的置换使得它能回到原先的状态。
题解
第一次做置换的题目
根据
保证\(m\)个个置换任意组合都可由其中一个代替,并且保证对于每一种置换都存在另外的置换使得它能回到原先的状态。
可知,这\(m\)个置换如果加上一个单位元,能够构成一个置换群。
根据\(Burnside\)引理,对于一个置换群,本质不同的染色方案数为:\(\frac{\sum 置换的不动点数量}{置换个数}\)
至于一个置换的不动点数量,需要我们利用DP去计算。
对于一个置换,我们可以把它分解成若干个循环置换,对于循环置换里面的每一个元素必须颜色相同。
\(f[i][j][k]\)表示三种颜色使用了i, j, k的方案数。
这样\(f[i][j][k] = f[i - sz][j][k] + f[i][j - sz][k] + f[i][j][k - sz]\)
一个循环节只能取一次,所以是个01背包,要倒序循环。
注意这里其实不是严格的置换群,因为群是一个集合,所以群里面的元素不能重复,要注意在读入时去重,不然会被luogu老哥的hack数据叉。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <queue>
#include <set>
#include <map>
#define int long long
#define Mid ((l + r) >> 1)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int read(){
char c; int num, f = 1;
while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
return f * num;
}
const int N = 69;
int r, g, b, n, m, p, tot;
int tr[N][N], f[N][N][N], len[N], vis[N];
//len[i]表示第i个循环置换的长度
void up(int &x, int y) { x = (x + y) % p; }
int cal(int *tr) {
tot = 0;
for(int i = 0; i <= r; i++) for(int j = 0; j <= b; j++) for(int k = 0; k <= g; k++) f[i][j][k] = 0;
for(int i = 1; i <= n; i++) vis[i] = 0;
for(int i = 1; i <= n; i++) if(!vis[i]){
int now = i, l = 0;
while(!vis[now]) {
vis[now] = 1;
l++;
now = tr[now];
}
len[++tot] = l;
}
f[0][0][0] = 1;
for(int i = 1; i <= tot; i++) for(int nr = r; ~nr; nr--) for(int nb = b; ~nb; nb--) for(int ng = g; ~ng; ng--){
if(nr >= len[i]) up(f[nr][nb][ng], f[nr - len[i]][nb][ng]);
if(nb >= len[i]) up(f[nr][nb][ng], f[nr][nb - len[i]][ng]);
if(ng >= len[i]) up(f[nr][nb][ng], f[nr][nb][ng - len[i]]);
}
return f[r][b][g];
}
int Pow(int a, int x) {
int ans = 1;
for( ; x; a = 1ll * a * a % p, x >>= 1)
if(x & 1)
ans = 1ll * ans * a % p;
return ans % p;
}
signed main()
{
int ans = 0, vis[N] = {0}, cnt;
r = read(); b = read(); g = read(); n = r + b + g;
m = read(); p = read();
for(int i = 1; i <= m; i++) for(int j = 1; j <= n; j++) tr[i][j] = read();
m++;
for(int i = 1; i <= n; i++) tr[m][i] = i;
cnt = m;
for(int i = 1; i <= m; i++) if(!vis[i]){
up(ans, cal(tr[i]));
for(int j = i + 1; j <= m; j++) if(!vis[j]){
int f = 1;
for(int k = 1; k <= n; k++) {
if(tr[i][k] != tr[j][k]) {
f = 0;
break;
}
}
if(f) {
vis[j] = 1;
cnt--;
}
}
}
printf("%lld\n", (1ll * ans * Pow(cnt, p - 2) % p));
return 0;
}