BZOJ 1004 [HNOI2008]Cards

 

因为那篇博客更不动了。。编辑一下要卡顿好久。。

还是一个题一个题更吧。。

一看到洗牌之后会等价这种就差不多是等价类计数,要用Burnside或者polya来计算。

看了好久才有点懂这部分究竟咋做。

首先要满足是一个置换群,那么就得补上单位元。

因为有颜色限制,所以不能polya,只能用Burnside。

Burnside的核心就是求出每一个置换有多少方案是不动的。

那么就是把每种置换独立开来,置换里的每个循环看成一个循环长度大小的盒子,然后用那么多颜色来染色。

可以用三维背包做,也可以用组合计数做。(如果可以,就不需要背包了。。。)

这里用了背包。

#include <bits/stdc++.h>

const int N = 67;
int dp[N][N][N], trans[N][N], sr, sb, sg, MOD, m, n;
int a[N];
bool vis[N];

void M(int &a) {
    if (a >= MOD) a -= MOD;
    if (a < 0) a += MOD;
}

int solve(int index) {
    memset(a, 0, sizeof(a));
    memset(vis, 0, sizeof(vis));
    memset(dp, 0, sizeof(dp));
    dp[0][0][0] = 1;
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (vis[i]) continue;
        ++cnt;
        int p = i;
        while (!vis[p]) {
            vis[p] = 1;
            a[cnt]++;
            p = trans[index][p];
        }
    }
    for (int i = 1; i <= cnt; i++) 
        for (int j = sr; j >= 0; j--)
            for (int k = sb; k >= 0; k--)
                for (int l = sg; l >= 0; l--) {
                    if (j >= a[i]) M(dp[j][k][l] += dp[j - a[i]][k][l]);
                    if (k >= a[i]) M(dp[j][k][l] += dp[j][k - a[i]][l]);
                    if (l >= a[i]) M(dp[j][k][l] += dp[j][k][l - a[i]]);
                }
    return dp[sr][sb][sg];
}

int qp(int a, int b = MOD - 2) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1LL * ans * a % MOD;
        a = 1LL * a * a % MOD;
        b >>= 1;
    }
    return ans % MOD;
}

int main() {
    scanf("%d%d%d%d%d", &sr, &sb, &sg, &m, &MOD);
    n = sr + sb + sg;
    for (int i = 0; i < m; i++)
        for (int j = 1; j <= n; j++)
            scanf("%d", trans[i] + j);
    for (int j = 1; j <= n; j++)
        trans[m][j] = j;
    int ans = 0;
    for (int i = 0; i <= m; i++)
        M(ans += solve(i));
    ans = 1LL * ans * qp(m + 1) % MOD;
    printf("%d\n", ans);
    return 0;
}
View Code
posted @ 2020-01-22 20:39  Mrzdtz220  阅读(100)  评论(0编辑  收藏  举报