BZOJ 1478 & 1488 & 1815 & Sgu282 Isomorphism

边染色,顶点的置换导致同构,暴力枚举置换有 $n!$ 种,考虑有多少种置换的贡献相同。

点的一个置换中,对顶点 $u$ 有 $f: u \to p[u]$,边 $(u, v)$ 的置换 $f':(u,v) \to (p[u], p[v])$。
当 $u, v$ 同时在一个循环中,循环长度为 $L$,那么这样的边 $(u,v)$ 循环个数为 $\lfloor \frac{L}{2} \rfloor$。
当 $u, v$ 分别在长度为 $L_1$,$L_2$ 的循环中时,边 $(u,v)$ 形成的循环长度为 $\text{lcm}(L_1, L_2)$,循环个数为 $\gcd(L_1, L_2)$。
循环个数只跟 $L$ 有关,那么看 $L$ 可以怎么构成。
现在就是把 $n$ 个顶点分成若干个循环。设有 $m$ 个循环,循环长度分别为 $L_1, L_2, ..., L_m$,其中 $0 < L_1 \leq L_2 \leq ... \leq L_m$,$L_1 + L_2 + ... + L_m = n$。
一种分法确定后,求一下有多少种置换。
相当于把 $n$ 个元素放进 $m$ 个盒子,每个盒子大小为 $L_i$,方案数为 $\frac{n!}{L_1!L_2!...L_m!}$,每个盒子里的有 $(L_i - 1)!$ 种排列方式,那么方案数为 $\frac{n!}{L_1L_2...L_m}$,若有 $L_i = L_{i+1} = ... =L_{i + k - 1}$,这 $k$ 个盒子是无区别的,那么就得再除以 $k!$。
所以 $L_1...L_m$ 确定后,就有 $$\frac{n!}{L_1L_2...L_mk_1!k_2!...k_t!}$$
其中 $t$ 表示 $L$ 有 $t$ 种取值,每种取值出现了 $k_i$ 次。

对于 BZOJ1488 这道图同构的,相当于给完全图的边染上两种颜色,白色表示该边存在,黑色表示该边不存在,就是 $m=2$ 的情况。

#include <bits/stdc++.h>
#define ll long long

const int N = 100;
int n, m, MOD;
int fac[N], Ans, tol;
int num[N], cnt[N];

int gcd(int a, int b) {
    while (b) {
        a %= b;
        std::swap(a, b);
    }
    return a;
}

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

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

template<class T>
void Mb(T &a) {
    static const int mod = MOD - 1;
    a %= mod;
    if (a >= mod) a -= mod;
    if (a < 0) a += mod;
}

void dfs(int cur, int left) {
    if (!left) {
        int a = 1;
        ll b = 0;
        for (int i = 1; i <= tol; i++) {
            a = 1LL * a * qp(num[i], cnt[i]) % MOD * fac[cnt[i]] % MOD;
            b += cnt[i] * (cnt[i] - 1) / 2 * num[i] + num[i] / 2 * cnt[i];
            for (int j = i + 1; j <= tol; j++)
                b += cnt[i] * cnt[j] * gcd(num[i], num[j]);
        }
        a = 1LL * fac[n] * qp(a) % MOD;
        Mb(b);
        Ma(Ans += 1LL * a * qp(m, (int)b) % MOD);
    }
    if (cur > left) return;
    dfs(cur + 1, left);
    for (int i = 1; i * cur <= left; i++) {
        ++tol;
        num[tol] = cur; cnt[tol] = i;
        dfs(cur + 1, left - i * cur);
        --tol;
    }
}

int main() {
    scanf("%d%d%d", &n, &m, &MOD);
    for (int i = fac[0] = 1; i < N; i++)
        fac[i] = 1ll * i * fac[i - 1] % MOD;
    dfs(1, n);
    printf("%lld\n", 1LL * Ans * qp(fac[n]) % MOD);
    return 0;
}
View Code

 

posted @ 2020-01-24 14:13  Mrzdtz220  阅读(132)  评论(0编辑  收藏  举报