WC2019数树(Matrix-Tree定理+容斥+树形dp+多项式exp)

题目链接

题目大意

题目给定点数 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

显然问题1是个SB题,如果两棵树中某条边重合,就直接缩起来,最后看有多少个缩起来之后的点即可。

问题2

问题2需要推一波式子。我们考虑计算至少 k k k条边在两棵树之间重合的方案数,这样实际上会把原树分成 n − k n-k nk个连通块。我们假设第 i i i个连通块的大小为 a i a_i ai,于是可以使用Matrix-Tree定理算出生成树个数是下面行列式的值(令 m = n − k m=n-k m=nk):
∣ ( 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 (na1)a1a2a1a3a1am1a1a1a2(na2)a2a3a2am1a2a1a3a2a3(na3)a3am1a3a1am1a2am1a3am1(nam1)am1=i=1m1aina1a1a1a1a2na2a2a2a3a2na3a3am1am1am1nam1=i=1m1ain0000n0000n0nnnni=1m1ai=nm2i=1mai
但是这样有可能会计算多,比如强制让某些边重合时,在连通块之间的边也重合了。如果真正重合的边集为 E E E,那么它会被在所有 S ⊆ E S\subseteq E SE中被计算恰好一次。我们希望最终 E E E的贡献恰好为 y n − ∣ E ∣ y^{n-|E|} ynE y n y^n yn可以提取出来最后乘,于是我们需要满足 ∑ S ⊆ E f ( ∣ S ∣ ) = y − ∣ E ∣ \sum_{S\subseteq E}f(|S|)=y^{-|E|} SEf(S)=yE,二项式反演可以得到: 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)ki(ik)yi=(y11)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 (y11)nmnm2i=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了。

问题3

有了问题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=nm!n!i=1mai!aiai2(nm2i=1mai)2(y11)nm=n4yn(y11)nn!i=1mai=nm!(n2(y11)1)mi=1mai!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(y11)nn!m1m!(F(x)n2(y11)1)m=[xn]n4yn(y11)nn!eF(x)n2(y11)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;
}

你可能感兴趣的:(dp,多项式)