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)=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}\) ,则有:
得证。
组合解释:
把给定的 \(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\) 。
那么有矩阵如下:
去掉最后一行,最后一列,得到:
接下来要算其行列式,首先化简矩阵,第 \(i (i>1)\) 行减去第 \(i-1\) 行 \(\times \dfrac{a_i}{a_{i-1}}\) 。
得到新的矩阵:
此时,除了第一行以外的每一行,只在 \(i-1,i\) 处有值,这样的特殊矩阵,带入行列式的表达式
有值的 \(p\) 只有 \(n\) 个,分成两类:
-
\(p_i=i,N(p)=0\),\(\prod A_{i,p_i}=(n-a_1)n^{m-2} \prod_{i<m}\limits a_i\)
-
\(p_1=q(q>1),p_j=\left\{\begin{aligned} j-1 && 2\leq j\leq q\\ j && q<j\end{aligned}\right.\)
则:
即方案数为 \(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)\) 。
答案即为:
点击查看代码
#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;
}