UOJ Round #20 T1 A. 【UR #20】跳蚤电话(组合数+树形DP)

UOJ Round #20 T1 A. 【UR #20】跳蚤电话

题目大意

  • 给出一棵树,求建出该树的不同操作方案数。建树方式如下:初始 S S S集合只有 1 1 1,操作 1 1 1为取已连的边 x , y x,y x,y和不在 S S S的点 z z z,删去边 ( x , y ) (x,y) (x,y),加入边 ( x , z ) , ( y , z ) (x,z),(y,z) (x,z),(y,z),再把 z z z放入 S S S;操作 2 2 2为取 S S S内的点 x x x S S S外的点 y y y,加入边 ( x , y ) (x,y) (x,y),再把 y y y放入 S S S
  • 2 ≤ n ≤ 1 0 5 2\le n \le 10^5 2n105

题解

  • 可以先分析操作,得出一些结论:
  • 1、 x ∈ S x\in S xS当且仅当 x x x 1 1 1连通;
  • 2、 S S S能在操作中出现(即合法)当且仅当 S S S中任意两点的 l c a lca lca也在 S S S中;
  • 3、任意一个合法的 S S S,只对应唯一一种树的形态,因此操作实质是按一定顺序不断加点;
  • 4、某个子树内的点的加入顺序与子树外无关。
  • 于是可以考虑树形DP,设 f i f_i fi表示 i i i子树内的不同顺序方案数,有两种转移。
  • 一、先加入子树根,再儿子内所有点保持各自相对顺序插入(用组合数);
  • 二、先加入某个儿子的部分点(枚举数量),再加入子树根,最后把该儿子剩下的点和其他儿子内所有点保持各自相对顺序插入。(当 i i i 1 1 1时不能使用这种转移)
  • 写出来后发现很多式子都是可以优化的。最后有一个部分式子形如 ( m 0 ) + ( m + 1 1 ) + ( m + 2 2 ) + . . . + ( m + k k ) {m\choose0}+{m+1\choose1}+{m+2\choose2}+...+{m+k\choose k} (0m)+(1m+1)+(2m+2)+...+(km+k),可以把 ( m 0 ) m\choose 0 (0m)看做是 ( m + 1 0 ) m+1\choose 0 (0m+1),然后在杨辉三角上就能不断合并下来,最后结果是 ( m + k + 1 k ) m+k+1\choose k (km+k+1)
  • 由于快速幂,最后复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100010
#define md 998244353
#define ll long long
int last[N], nxt[N * 2], to[N * 2], len = 0;
int si[N], f[N];
ll F[N], G[N];
int ans = 0;
ll ksm(ll x, ll y) {
	if(!y) return 1;
	ll l = ksm(x, y / 2);
	if(y % 2) return l * l % md * x % md;
	return l * l % md;
}
ll C(int x, int y) {
	return F[x] * G[y] % md * G[x - y] % md;
}
void add(int x, int y) {
	to[++len] = y;
	nxt[len] = last[x];
	last[x] = len;
}
void dfs(int k, int fa) {
	si[k] = 1;
	ll sum = 1;
	for(int i = last[k]; i; i = nxt[i]) if(to[i] != fa) {
		dfs(to[i], k);
		si[k] += si[to[i]];
		sum = sum * C(si[k] - 1, si[to[i]]) % md * f[to[i]] % md;
	}
	f[k] = sum;
	if(k == 1) return;
	for(int i = last[k]; i; i = nxt[i]) if(to[i] != fa) {
		int x = to[i];
		ll s = C(si[k] - 1, si[x] - 1);
		ll t = f[x] * sum % md * ksm(f[x] * C(si[k] - 1, si[x]) % md, md - 2) % md;
		f[k] = (f[k] + s * t % md) % md;
	}
}
int main() {
	int n, i, x, y;
	scanf("%d", &n);
	F[0] = G[0] = 1;
	for(i = 1; i <= n; i++) F[i] = F[i - 1] * i % md;
	G[n] = ksm(F[n], md - 2);
	for(i = n - 1; i; i--) G[i] = G[i + 1] * (i + 1) % md;
	for(i = 1; i < n; i++) {
		scanf("%d%d", &x, &y);
		add(x, y), add(y, x);
	}
	dfs(1, 0);
	printf("%lld\n", f[1]);
	return 0;
}

自我小结

  • 从头到尾想法都比较自然,思路还是很清晰的,但是式子在实现上容易出现细节问题。
posted @ 2021-04-05 20:00  AnAn_119  阅读(135)  评论(0编辑  收藏  举报