• 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 2le n le 10^5 2n105

    题解

    • 可以先分析操作,得出一些结论:
    • 1、 x ∈ S xin 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 ) {mchoose0}+{m+1choose1}+{m+2choose2}+...+{m+kchoose k} (0m)+(1m+1)+(2m+2)+...+(km+k),可以把 ( m 0 ) mchoose 0 (0m)看做是 ( m + 1 0 ) m+1choose 0 (0m+1),然后在杨辉三角上就能不断合并下来,最后结果是 ( m + k + 1 k ) m+k+1choose k (km+k+1)
    • 由于快速幂,最后复杂度是 O ( n log ⁡ n ) O(nlog 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
    ", f[1]);
    	return 0;
    }
    

    自我小结

    • 从头到尾想法都比较自然,思路还是很清晰的,但是式子在实现上容易出现细节问题。
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    English Dictionary site for ODE and OALD
    vmic environment
    makefile
    the diference between include and import
    windows 工具命令 cmd
    python namespace
    shell cmd args
    ROE, ROC
    IP
    链接及常用软件
  • 原文地址:https://www.cnblogs.com/LZA119/p/14623427.html
Copyright © 2020-2023  润新知