【ybt金牌导航8-5-6】【luogu P1446】卡牌染色 / Cards(Burnside引理)(背包)
卡牌染色 / Cards
题目链接:ybt金牌导航8-5-6 / luogu P1446
题目大意
有 n 张牌,分别由三个颜色组成,每个颜色告诉你个数。
然后还会告诉你一些允许的置换方案,保证若干次置换都可以被其中的一个置换代替。
然后问你有多少种本质不同的排法,两种本质相同时可以通过置换把它们变成一样的。
思路
看到置换,然后颜色,考虑用 Polya 定理。
然而你发现颜色不是随便选,但是由于颜色很少,我们考虑用 Burnside 引理。
那显然根据题目置换的特殊条件,所有的置换就是它给出的加上不变的置换。(当然要处理给出的置换就有不变的置换的情况)
那显然我们可以直接找到每个循环,然后每个循环里面的的点都要同一个颜色。
然后你发现直接背包复杂度过得去,然后背包搞即可。
就 \(f_{i,j,k}\) 为三个颜色选的个数分别为 \(i,j,k\) 的方案数,然后每次会放入一个当前循环大小的在三个颜色的其中一个转移即可。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int sr, sb, sg, m, mo, n, f[21][21][21];
int a[101], in[101], num, l[101];
int ans, inv[101];
int ksm(int x, int y) {
int re = 1;
while (y) {
if (y & 1) re = re * x % mo;
x = x * x % mo;
y >>= 1;
}
return re;
}
void work(int num) {
memset(f, 0, sizeof(f));
f[0][0][0] = 1;
for (int i = 1; i <= num; i++) {//背包
for (int x = sr; x >= 0; x--)
for (int y = sb; y >= 0; y--)
for (int z = sg; z >= 0; z--) {
if (!f[x][y][z]) continue;
if (x + l[i] <= sr) f[x + l[i]][y][z] = (f[x + l[i]][y][z] + f[x][y][z]) % mo;
if (y + l[i] <= sb) f[x][y + l[i]][z] = (f[x][y + l[i]][z] + f[x][y][z]) % mo;
if (z + l[i] <= sg) f[x][y][z + l[i]] = (f[x][y][z + l[i]] + f[x][y][z]) % mo;
}
}
ans = (ans + f[sr][sb][sg]) % mo;
}
bool cksame() {
for (int i = 1; i <= n; i++)
if (a[i] != i) return 0;
return 1;
}
int main() {
scanf("%d %d %d %d %d", &sr, &sb, &sg, &m, &mo);
n = sr + sb + sg;
for (int j = 1; j <= n; j++) a[j] = j;//记得有不动这个置换
num = n; for (int i = 1; i <= n; i++) l[i] = 1;
work(num);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) scanf("%d", &a[j]);
if (cksame()) {//然后不动的置换也可能给出,要忽略掉
i--; m--;//同时总的置换数要减一
continue;
}
num = 0;
for (int j = 1; j <= n; j++)
if (in[j] != i) {
num++; int now = a[j]; l[num] = 1; in[j] = i;
while (now != j) {
l[num]++; in[now] = i; now = a[now];
}
}
work(num);
}
ans = ans * ksm(m + 1, mo - 2) % mo;//记得加上不动的置换(如果重复在前面减去了)
printf("%d", ans);
return 0;
}