题目链接
题目给定点数 n n n和颜色数 m m m,分为三个问题:
1.给定两棵树,规定对于 u , v u,v u,v,若边 ( u , v ) (u,v) (u,v)同时在两棵树中出现,则 u , v u,v u,v必须染同种颜色。
2.给定一棵树,求对于所有第二棵树的可能出现情况,问题1的答案之和。
3.给定零棵树,求对于所有第一棵树的可能出现情况,问题2的答案之和。
显然问题1是个SB题,如果两棵树中某条边重合,就直接缩起来,最后看有多少个缩起来之后的点即可。
问题2需要推一波式子。我们考虑计算至少有 k k k条边在两棵树之间重合的方案数,这样实际上会把原树分成 n − k n-k n−k个连通块。我们假设第 i i i个连通块的大小为 a i a_i ai,于是可以使用Matrix-Tree定理算出生成树个数是下面行列式的值(令 m = n − k m=n-k m=n−k):
∣ ( n − a 1 ) a 1 − a 1 a 2 − a 1 a 3 ⋯ − a 1 a m − 1 − a 2 a 1 ( n − a 2 ) a 2 − a 2 a 3 ⋯ − a 2 a m − 1 − a 3 a 1 − a 3 a 2 ( n − a 3 ) a 3 ⋯ − a 3 a m − 1 ⋯ ⋯ ⋯ ⋯ ⋯ − a m − 1 a 1 − a m − 1 a 2 − a m − 1 a 3 ⋯ ( n − a m − 1 ) a m − 1 ∣ = ∏ i = 1 m − 1 a i ∣ n − a 1 − a 2 − a 3 ⋯ − a m − 1 − a 1 n − a 2 − a 2 ⋯ − a m − 1 − a 1 − a 2 n − a 3 ⋯ − a m − 1 ⋯ ⋯ ⋯ ⋯ ⋯ − a 1 − a 2 − a 3 ⋯ n − a m − 1 ∣ = ∏ i = 1 m − 1 a i ∣ n 0 0 ⋯ − n 0 n 0 ⋯ − n 0 0 n ⋯ − n ⋯ ⋯ ⋯ ⋯ ⋯ 0 0 0 ⋯ n − ∑ i = 1 m − 1 a i ∣ = n m − 2 ∏ i = 1 m a i \left|\begin{matrix} (n-a_1)a_1 & -a_1a_2 & -a_1a_3 & \cdots & -a_1a_{m-1} \\ -a_2a_1 & (n-a_2)a_2 & -a_2a_3 & \cdots & -a_2a_{m-1} \\ -a_3a_1 & -a_3a_2 & (n-a_3)a_3 & \cdots & -a_3a_{m-1} \\ \cdots & \cdots & \cdots & \cdots & \cdots\\ -a_{m-1}a_1 & -a_{m-1}a_2 & -a_{m-1}a_3 & \cdots & (n-a_{m-1})a_{m-1} \end{matrix}\right| \\ =\prod_{i=1}^{m-1}a_i\left|\begin{matrix} n-a_1 & -a_2 & -a_3 & \cdots & -a_{m-1} \\ -a_1 & n-a_2 & -a_2 & \cdots & -a_{m-1} \\ -a_1 & -a_2 & n-a_3 & \cdots & -a_{m-1} \\ \cdots & \cdots & \cdots & \cdots & \cdots\\ -a_1 & -a_2 & -a_3 & \cdots & n-a_{m-1} \end{matrix}\right| \\ =\prod_{i=1}^{m-1}a_i\left|\begin{matrix} n & 0 & 0 & \cdots & -n \\ 0 & n & 0 & \cdots & -n \\ 0 & 0 & n & \cdots & -n \\ \cdots & \cdots & \cdots & \cdots & \cdots\\ 0 & 0 & 0 & \cdots & n-\sum_{i=1}^{m-1}a_i \end{matrix}\right| \\ =n^{m-2}\prod_{i=1}^ma_i ∣∣∣∣∣∣∣∣∣∣(n−a1)a1−a2a1−a3a1⋯−am−1a1−a1a2(n−a2)a2−a3a2⋯−am−1a2−a1a3−a2a3(n−a3)a3⋯−am−1a3⋯⋯⋯⋯⋯−a1am−1−a2am−1−a3am−1⋯(n−am−1)am−1∣∣∣∣∣∣∣∣∣∣=i=1∏m−1ai∣∣∣∣∣∣∣∣∣∣n−a1−a1−a1⋯−a1−a2n−a2−a2⋯−a2−a3−a2n−a3⋯−a3⋯⋯⋯⋯⋯−am−1−am−1−am−1⋯n−am−1∣∣∣∣∣∣∣∣∣∣=i=1∏m−1ai∣∣∣∣∣∣∣∣∣∣n00⋯00n0⋯000n⋯0⋯⋯⋯⋯⋯−n−n−n⋯n−∑i=1m−1ai∣∣∣∣∣∣∣∣∣∣=nm−2i=1∏mai
但是这样有可能会计算多,比如强制让某些边重合时,在连通块之间的边也重合了。如果真正重合的边集为 E E E,那么它会被在所有 S ⊆ E S\subseteq E S⊆E中被计算恰好一次。我们希望最终 E E E的贡献恰好为 y n − ∣ E ∣ y^{n-|E|} yn−∣E∣, y n y^n yn可以提取出来最后乘,于是我们需要满足 ∑ S ⊆ E f ( ∣ S ∣ ) = y − ∣ E ∣ \sum_{S\subseteq E}f(|S|)=y^{-|E|} ∑S⊆Ef(∣S∣)=y−∣E∣,二项式反演可以得到: f ( k ) = ∑ i = 0 k ( − 1 ) k − i ( k i ) y − i = ( 1 y − 1 ) k f(k)=\sum_{i=0}^k(-1)^{k-i}\binom kiy^{-i}=(\frac 1y-1)^k f(k)=∑i=0k(−1)k−i(ik)y−i=(y1−1)k。
也就是说我们把树分成 m m m个连通块,第 i i i块的大小为 a i a_i ai,那么它对答案的贡献就是 ( y − 1 − 1 ) n − m n m − 2 ∏ i = 1 m a i (y^{-1}-1)^{n-m}n^{m-2}\prod_{i=1}^ma_i (y−1−1)n−mnm−2∏i=1mai。
于是 O ( n 2 ) O(n^2) O(n2)的dp应该比较显然了,记一下根所在的连通块大小即可。
我们再考虑 ∏ a i \prod a_i ∏ai的组合意义,它相当于在每个连通块中选一个点的总方案数。于是令 f [ i ] f[i] f[i]表示 i i i所在的连通块中选了点的方案数, g [ i ] g[i] g[i]表示 i i i所在连通块中没有选点。于是就可以 O ( n ) O(n) O(n)的dp了。
有了问题2的推导,问题3的算式还是比较明显的。首先暴力枚举连通块划分求值:
∑ ∑ i = 1 m a i = n n ! ∏ i = 1 m a i a i − 2 a i ! m ! ( n m − 2 ∏ i = 1 m a i ) 2 ( y − 1 − 1 ) n − m = y n ( y − 1 − 1 ) n n ! n 4 ∑ ∑ i = 1 m a i = n ( n 2 ( y − 1 − 1 ) − 1 ) m m ! ∏ i = 1 m a i a i a i ! \sum_{\sum_{i=1}^ma_i=n}\frac{n!\prod_{i=1}^m\frac{a_i^{a_i-2}}{a_i!}}{m!}\left(n^{m-2}\prod_{i=1}^ma_i\right)^2(y^{-1}-1)^{n-m} \\ =\frac{y^n(y^{-1}-1)^nn!}{n^4}\sum_{\sum_{i=1}^ma_i=n}\frac{(n^2(y^{-1}-1)^{-1})^m}{m!}\prod_{i=1}^m\frac{a_i^{a_i}}{a_i!} ∑i=1mai=n∑m!n!∏i=1mai!aiai−2(nm−2i=1∏mai)2(y−1−1)n−m=n4yn(y−1−1)nn!∑i=1mai=n∑m!(n2(y−1−1)−1)mi=1∏mai!aiai
EGF的形式已经出来了。令 F ( x ) = ∑ i i i ! x i F(x)=\sum \frac{i^i}{i!}x^i F(x)=∑i!iixi,则上式为:
[ x n ] y n ( y − 1 − 1 ) n n ! n 4 ∑ m ≥ 1 ( F ( x ) n 2 ( y − 1 − 1 ) − 1 ) m m ! = [ x n ] y n ( y − 1 − 1 ) n n ! n 4 e F ( x ) n 2 ( y − 1 − 1 ) − 1 [x^n]\frac{y^n(y^{-1}-1)^nn!}{n^4}\sum_{m\ge 1}\frac{(F(x)n^2(y^{-1}-1)^{-1})^m}{m!} \\ =[x^n]\frac{y^n(y^{-1}-1)^nn!}{n^4}e^{F(x)n^2(y^{-1}-1)^{-1}} [xn]n4yn(y−1−1)nn!m≥1∑m!(F(x)n2(y−1−1)−1)m=[xn]n4yn(y−1−1)nn!eF(x)n2(y−1−1)−1
因此直接多项式exp即可。复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
多项式板子太长就不放了qwq
int n, m, typ;
namespace Solve0 {
int par[MAXN], vis[MAXN];
set<pair<int, int> > ss;
int find(int x) { return par[x] == x ? x : par[x] = find(par[x]); }
void solve() {
for (int i = 1; i <= n; i++) par[i] = i;
for (int i = 1; i < n; i++) {
int u, v; read(u, v);
ss.insert(make_pair(u, v));
ss.insert(make_pair(v, u));
}
for (int i = 1; i < n; i++) {
int u, v; read(u, v);
if (ss.find(make_pair(u, v)) != ss.end())
par[find(u)] = find(v);
}
ll res = 1;
for (int i = 1; i <= n; i++) if (!vis[find(i)])
vis[find(i)] = 1, res = res * m % MOD;
printf("%lld\n", res);
}
}
namespace Solve1 {
struct Edge { int to, next; } edge[MAXN];
int head[MAXN], tot;
ll f[MAXN], g[MAXN], invn, invm;
void addedge(int u, int v) {
edge[++tot] = (Edge) { v, head[u] };
head[u] = tot;
}
void dfs(int u, int fa) {
f[u] = g[u] = invn;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa) continue;
dfs(v, u);
ll a = (f[u] * f[v] % MOD * n % MOD * n + (f[u] * g[v] + g[u] * f[v]) % MOD * n % MOD * (invm - 1)) % MOD;
ll b = (g[u] * f[v] % MOD * n % MOD * n + g[u] * g[v] % MOD * n % MOD * (invm - 1)) % MOD;
f[u] = a, g[u] = b;
}
}
void solve() {
for (int i = 1; i < n; i++) {
int u, v; read(u, v);
addedge(u, v), addedge(v, u);
}
invn = modpow(n, MOD - 2);
invm = modpow(m, MOD - 2);
dfs(1, 0);
printf("%lld\n", f[1] * modpow(m, n) % MOD);
}
}
namespace Solve2 {
ll fac[MAXN], rev[MAXN]; int A[MAXN], B[MAXN];
void solve() {
if (m == 1) { printf("%d\n", modpow(n, 2 * n - 4)); return; }
for (int i = fac[0] = 1; i <= n; i++) fac[i] = fac[i - 1] * i % MOD;
rev[n] = modpow(fac[n], MOD - 2);
for (int i = n; i > 0; i--) rev[i - 1] = rev[i] * i % MOD;
ll invm = modpow(m, MOD - 2), iinvm = (ll)modpow(invm - 1, MOD - 2) * n % MOD * n % MOD;
for (int i = 1; i <= n; i++) A[i] = modpow(i, i) * rev[i] % MOD * iinvm % MOD;
get_exp(A, B, get_tpow(n + 1));
printf("%lld\n", (ll)B[n] * modpow(n, MOD - 5) % MOD * fac[n] % MOD * modpow(m, n) % MOD * modpow(invm - 1, n) % MOD);
}
}
int main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
read(n, m, typ);
if (typ == 0) Solve0::solve();
else if (typ == 1) Solve1::solve();
else Solve2::solve();
return 0;
}