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

题目链接

题目大意

题目给定点数 n n 和颜色数 m m ,分为三个问题:
1.给定两棵树,规定对于 u , v u,v ,若边 ( u , v ) (u,v) 同时在两棵树中出现,则 u , v u,v 必须染同种颜色。
2.给定一棵树,求对于所有第二棵树的可能出现情况,问题1的答案之和。
3.给定零棵树,求对于所有第一棵树的可能出现情况,问题2的答案之和。

题解

问题1

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

问题2

问题2需要推一波式子。我们考虑计算至少 k k 条边在两棵树之间重合的方案数,这样实际上会把原树分成 n k n-k 个连通块。我们假设第 i i 个连通块的大小为 a i a_i ,于是可以使用Matrix-Tree定理算出生成树个数是下面行列式的值(令 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
但是这样有可能会计算多,比如强制让某些边重合时,在连通块之间的边也重合了。如果真正重合的边集为 E E ,那么它会被在所有 S E S\subseteq E 中被计算恰好一次。我们希望最终 E E 的贡献恰好为 y n E y^{n-|E|} y n y^n 可以提取出来最后乘,于是我们需要满足 S E f ( S ) = y E \sum_{S\subseteq E}f(|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
也就是说我们把树分成 m m 个连通块,第 i i 块的大小为 a i a_i ,那么它对答案的贡献就是 ( 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
于是 O ( n 2 ) O(n^2) 的dp应该比较显然了,记一下根所在的连通块大小即可。

我们再考虑 a i \prod a_i 的组合意义,它相当于在每个连通块中选一个点的总方案数。于是令 f [ i ] f[i] 表示 i i 所在的连通块中选了点的方案数, g [ i ] g[i] 表示 i i 所在连通块中没有选点。于是就可以 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!}
EGF的形式已经出来了。令 F ( x ) = i i i ! x i F(x)=\sum \frac{i^i}{i!}x^i ,则上式为:
[ 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}}
因此直接多项式exp即可。复杂度 O ( n l o g n ) 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;
}

猜你喜欢

转载自blog.csdn.net/WAautomaton/article/details/86751832