Cayley 定理与扩展 Cayley 定理

Cayley 定理

节点个数为 \(n\) 的无根标号树的个数为 \(n^{n−2}\)

这个结论在很多计数类题目中出现,要证明它首先需要了解 \(\text{Prufer}\) 序列的相关内容。接下来给出证明。

证明:

每一棵树都可以转换为一个 \(\text{Prufer}\) 序列。

根据定义,每一个节点在 \(\text{Prufer}\) 序列中出现的次数等于该节点度数减一,即 \(d_i–1\)。整个 \(\text{Prufer}\) 序列的长度为 \(∑_id_i–1=2(n−1)–n=n–2\)

无根树的形态可以与 \(\text{Prufer}\) 序列一一对应,所以无根树的个数也等于 \(\text{Prufer}\) 序列的个数。故个数为 \(n−2\)

扩展 Cayley 定理 1

\(n\) 个标号节点形成一个有 \(s\) 颗树的森林且给定的 \(s\) 个点没有两个点属于同一颗树的方案数个数为 \(sn^{n-s-1}\)

证明:记其为 \(F(n,s)\)\(1\le s\le n\)),其中被钦定的 \(s\) 个不在同一棵树的点的标号是 \([n-s+1,n]\) 。枚举 \(n\) 的度数 \(j\) ,则得到以下递推式:

\[F(n,s)=\sum_{j=0}^{n-s}\binom{n-s}{j}F(n-1,s-1+j) \]

下面使用数学归纳法来证明 \(F(n,s)=sn^{n-s-1}\)

\(n=1\) 时显然成立。

\(n>1\) 时,若 \(n-1\) 成立(即对于 \(1\le k\le n-1\)\(F(n-1,k)=k(n-1)^{n-1-k}\) ,则有:

\[F(n,s)=\sum_{j=0}^{n-s}\binom{n-s}{j}(s-1+j)(n-1)^{n-s-j-1} \\ =\frac{1}{n-1}\sum_{j=0}^{n-s}\binom{n-s}{j}(s-1)(n-1)^{n-s-j}+\frac{1}{n-1}\sum_{j=0}^{n-s}\binom{n-s}{j}j(n-1)^{n-s-j} \\ =\frac{s-1}{n-1}\sum_{j=0}^{n-s}\binom{n-s}{j}(n-1)^{n-s-j}+\frac{n-s}{n-1}\sum_{j=0}^{n-s}\binom{n-s-1}{j-1}(n-1)^{n-s-j} \\ =\frac{s-1}{n-1}n^{n-s}+\frac{n-s}{n-1}n^{n-s-1}=\frac{sn-s}{n-1}n^{n-s-1}=sn^{n-s-1} \]

得证。

组合解释:

把给定的 \(s\) 个点看成被钦定的根结点,先当作没给定,然后再除以 \(\Large\binom{n}{s}\) 。加一个虚点 \(0\),把所有根连成它的儿子,变成 \(n+1\) 个点。钦定 \(0\) 的度数为 \(s\) 的树的个数。

\(\text{prufer}\) 序列的长度是 \(n-1\),钦定其中有 \(s-1\) 个是 \(0\),剩下的是 \(1,2,\cdots , n\) 。则方案数是 \({\large\binom{n-1}{s-1}}\times n^{n-s}\) 。然后再除以 \(\Large\binom{n}{s}\) ,得到 \(sn^{n-s-1}\)

扩展 Cayley 定理 2

对于一棵 \(n\) 个节点有标号无根树,已经被若干条边分成了大小分别为的若干连通块 \(a_1,a_2,\cdots, a_m\),连成一棵树的方案数为 \(\prod a_i \times n^{m-2}\)

\(\text{Matrix-Tree}\) 定理证明:

将每一个连通块压在一起考虑,此时有连通块 \(a_i,a_j\) 之间的边数即为 \(a_i\cdot a_j\)

那么有矩阵如下:

\[\left(\begin{array}{ccccc} a_1(n-a_1)& -a_1a_2 & -a_1a_3 & \cdots & -a_1a_m\\ -a_2a_1& a_2(n-a_2) & -a_2a_3 & \cdots & -a_2a_m \\ & & \ddots & & \\ -a_{m-1}a_1 & -a_{m-1} a_2 & -a_{m-1}a_3 & \cdots & -a_{m-1}a_m\\ -a_{m}a_1 & -a_{m} a_2 & -a_{m}a_3 & \cdots & a_m(n-a_m)\end{array}\right) \]

去掉最后一行,最后一列,得到:

\[\left(\begin{array}{ccccc} a_1(n-a_1)& -a_1a_2 & -a_1a_3 & \cdots & -a_1a_{m-1}\\ -a_2a_1& a_2(n-a_2) & -a_2a_3 & \cdots & -a_2a_{m-1} \\ & & \ddots & & \\ -a_{m-1}a_1 & -a_{m-1} a_2 & -a_{m-1}a_3 & \cdots & a_{m-1}(n-a_{m-1})\end{array}\right) \]

接下来要算其行列式,首先化简矩阵,第 \(i (i>1)\) 行减去第 \(i-1\)\(\times \dfrac{a_i}{a_{i-1}}\)

得到新的矩阵:

\[\left(\begin{array}{cccccc} a_1(n-a_1)& -a_1a_2 & -a_1a_3 & \cdots & -a_1a_{m-2} & -a_1a_{m-1}\\ -a_2n& a_2n & 0 & \cdots & 0& 0 \\ & & & \ddots & & \\ 0 & 0 & 0 & \cdots & -a_{m-1}n & a_{m-1}n\end{array}\right) \]

此时,除了第一行以外的每一行,只在 \(i-1,i\) 处有值,这样的特殊矩阵,带入行列式的表达式

\[\displaystyle \det(A)=\sum_{\text{p is a permutation}} (-1)^{N(p)} \prod A_{i,p_i} \]

有值的 \(p\) 只有 \(n\) 个,分成两类:

  1. \(p_i=i,N(p)=0\)\(\prod A_{i,p_i}=(n-a_1)n^{m-2} \prod_{i<m}\limits a_i\)

  2. \(p_1=q(q>1),p_j=\left\{\begin{aligned} j-1 && 2\leq j\leq q\\ j && q<j\end{aligned}\right.\)

\[\prod A_{i,p_i}=(-1)^{q-1} (-n)^{q} n^{m-1-q}a_q \prod_{i<m}\limits a_i=-n^{m-2} a_q\prod_{i<m}\limits a_i \]

则:

\[\det=(n-a_1-a_2-\cdots-a_{m-1})n^{m-2} \prod_{i<m}\limits a_i=a_mn^{m-2} \prod_{i<m}\limits a_i=n^{m-2} \prod a_i \]

即方案数为 \(n^{m-2} \prod a_i\)

CF1109D Sasha and Interesting Fact from Graph Theory

给定参数 \(n,m,a,b\) ,你现在要构造一颗 \(n\) 个点树,树边的权值可以赋为 \([1,m]\) 中的一个整数。
求有多少种构造树的方法,使得节点 \(a\) 与节点 \(b\) 在树上的最短路径恰好为 \(m\) ,答案对 \(10^9+7\) 取模。

然后枚举 \(a\)\(b\) 之间的边数 \(e\)。首先,除了 \(a\)\(b\) 两个点以外有 \(n-2\) 个点,从中选出 \(e-1\) 个排成 \(a\)\(b\) 中间的点,方案数是 \((n-2) ^ {\underline{e-1}}\)

\(a\)\(b\) 权值和为 \(m\) 且每个边权都在 \([1,m]\) 中,所以是在 \(a\)\(b\) 之间插板,方案数是 \(\binom{m-1}{e-1}\)

剩下的 \(n-e-1\) 个数的权值可以任取,则方案数是 \(m^{n-e-1}\) 。因为是这条链上 \(e+1\) 个点都挂一棵树,所以再乘上 \(F(n,e+1)\)

答案即为:

\[\sum_{e=1}^{n-1} (n-2) ^ {\underline{e-1}}\times \binom{m-1}{e-1}\times m^{n-e-1}\times F(n,e+1) \]

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m, a, b;
const int md = 1e9 + 7;
int fac[1000005], inv[1000005], F[1000005];
inline int pwr(int x, int y) {
    int res = 1; y = (y + md - 1) % (md - 1);
    while (y) {
        if (y & 1) res = 1ll * res * x % md;
        x = 1ll * x * x % md; y >>= 1;
    }
    return res;
}
inline void init() {
    fac[0] = fac[1] = inv[0] = inv[1] = 1;
    for (int i = 2; i <= 1e6; i++) fac[i] = 1ll * fac[i - 1] * i % md;
    for (int i = 2; i <= 1e6; i++) inv[i] = 1ll * (md - md / i) * inv[md % i] % md;
    for (int i = 2; i <= 1e6; i++) inv[i] = 1ll * inv[i] * inv[i - 1] % md;
    for (int i = 1; i <= 1e6; i++) F[i] = 1ll * i * pwr(n, n - i - 1) % md;
}
inline int C(int x, int y) {
    if (x < y) return 0;
    return 1ll * fac[x] * inv[y] % md * inv[x - y] % md;
}
int main() {
    scanf("%d%d%d%d", &n, &m, &a, &b);
    init();
    int res = 0;
    for (int i = 1; i < n; i++) {
        int tmp = 1ll * fac[n - 2] * inv[n - 1 - i] % md * C(m - 1, i - 1) % md * pwr(m, n - i - 1) % md * F[i + 1] % md;
        res = (res + tmp) % md;
    }
    printf("%d\n", res);

    return 0;
}

「NOI2022模拟赛czx2」图

给定一张图 \(G = (V = {1, 2, · · · , n}, E = {(u_1, v_1), (u_2, v_2), · · · , (u_m, v_m)})\)
先将其复制 \(k\) 份,得到一张 \(nk\) 个点,\(mk\) 条边的图 \(G′\),设其补图为 \(H\) ,求 \(H\) 的生成树数量 \(\pmod P\)

考虑进行容斥,强制出现 \(G\) 中的若干条边,对于剩下的部分,由 \(\text{Ex-Cayley}\) 定理可知,答案为 \(∏ s_i· (nk)^{c−2}\) ,其中 \(s_i\) 表示每一个连通块的大小,\(c\) 表示连通块的数量。

若能求出 \(G\) 中生成 \(i\) 个连通块的答案,添加 \((nk)^i\) 的系数后做 \(k\) 次幂,就也能求出 \(G′\) 的总答案。

考虑使用 \(\text{Matrix-Tree}\) 定理求解 \(G′\) 的答案,设:\(f_c =∑ ∏ s_i· (nk)^c\)

添加 \(0\) 号点,\(0\) 向每一个节点连接一条权为 \(x\) 的边,求出生成树。

则每一个连通块有 \(s_i\) 种向根节点连边的方案,且每一个连通块会使得最终的总权值 \(×x\)

即求出的行列式为一个多项式 \(f(x)\)\([x^c]f(x) = f_c\) ,观察可以发现,我们并不需要知道整个多项式,只需要知道 \(∑(−nk)^if_i\) ,即 \(f(−nk)\) 即可。

再考虑模数不是质数的情况,高斯消元可以采用辗转相除法,复杂度依然为 \(O(n^4)\) ,而算法 \(2\) 和算法 \(3\) 在计算的最后均需要添加系数 \((nk)^{−2}\),而 \(nk\) 可能不存在逆元。

容易发现 \(f(x)\) 并不存在常数项,因此可以将 \(i\to 0\) 的边权设为 \(1\) ,而 \(0\to i\) 的边权仍为 \(x\)

再将根节点设为 \(0\) 以外的点即可求解出 \(\frac f x(−nk)\),最后添加 \((nk)^{k−2}\) 作为系数即可(\(k = 1\) 时需要特判)。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m, k, md;
inline int pwr(int x, int y) {
    int res = 1;
    while (y) {
        if (y & 1)
            res = 1ll * res * x % md;
        x = 1ll * x * x % md;
        y >>= 1;
    }
    return res;
}
struct mat {
    int a[105][105];
    inline int* operator[](int t) { return a[t]; }
    inline int det() {
        int res = 1;
        for (int i = 0; i < n; i++) {
            int loc = i;
            for (int j = n - 1; j >= i; j--) if (a[j][i]) loc = j;
            if (loc != i) swap(a[i], a[loc]), res = -res;
            if (!a[i][i]) return 0;
            for (int j = i + 1; j < n; j++) {
                while (a[j][i]) {
                    int tmp = a[i][i] / a[j][i];
                    for (int t = i; t < n; t++) a[i][t] = (a[i][t] - 1ll * tmp * a[j][t]) % md;
                    swap(a[i], a[j]); res = -res;
                }
            }
        }
        for (int i = 0; i < n; i++) res = 1ll * res * a[i][i] % md;
        return (res + md) % md;
    }
} mp;
int main() {
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);
    scanf("%d%d%d%d", &n, &m, &k, &md);
    if (k == 1) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) mp[i][j] = n - 1;
                else mp[i][j] = -1;
            }
        }
        for (int i = 1; i <= m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            mp[x - 1][x - 1]--; mp[y - 1][y - 1]--;
            mp[x - 1][y - 1] = mp[y - 1][x - 1] = 0;
        }
        n--;
        printf("%d\n", mp.det());
    } else {
        for (int i = 1; i <= m; i++) {
            int x, y; scanf("%d%d", &x, &y);
            mp[x][x]++; mp[y][y]++;
            mp[x][y] = mp[y][x] = -1;
        }
        int nk = 1ll * n * k % md;
        for (int i = 1; i <= n; i++) {
            mp[0][0] = (mp[0][0] - 1) % md;
            mp[i][i] = (mp[i][i] - nk) % md;
            mp[i][0] = (mp[i][0] + nk) % md;
            mp[0][i] = (mp[0][i] + 1) % md;
        }
        int res = pwr(mp.det() * (n & 1 ? md - 1ll : 1ll) % md, k);
        printf("%lld\n", 1ll * res * pwr(nk, k - 2) % md);
    }

    return 0;
}

posted @ 2022-06-27 19:43  一粒夸克  阅读(771)  评论(0编辑  收藏  举报