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 2≤n≤105
题解
- 可以先分析操作,得出一些结论:
- 1、 x ∈ S x\in S x∈S当且仅当 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;
}
自我小结
- 从头到尾想法都比较自然,思路还是很清晰的,但是式子在实现上容易出现细节问题。
哈哈哈哈哈哈哈哈哈哈