• 洛谷P5206 [WC2019] 数树(数学)


    洛谷P5206 [WC2019] 数树(数学)

    题目描述

    本题包含三个问题:

    • 问题 0:已知两棵 (n) 个节点的树的形态(两棵树的节点标号均为 (1)(n)),其中第一棵树是红树,第二棵树是蓝树。要给予每个节点一个 ([1, y]) 中的整数,使得对于任意两个节点 (p, q),如果存在一条路径 ((a_1 = p, a_2, cdots , a_m = q)) 同时属于这两棵树,则 (p, q) 必须被给予相同的数。求给予数的方案数。
      • 存在一条路径同时属于这两棵树的定义见「题目背景」。
    • 问题 1:已知蓝树,对于红树的所有 (n^{n-2}) 种选择方案,求问题 0 的答案之和。
    • 问题 2:对于蓝树的所有 (n^{n-2}) 种选择方案,求问题 1 的答案之和。

    提示:(n) 个节点的树一共有 (n^{n-2}) 种。

    在不同的测试点中,你将可能需要回答不同的问题。我们将用 ( ext{op}) 来指代你需要回答的问题编号(对应上述 0、 1、 2)。

    由于答案可能很大,因此你只需要输出答案对 (998, 244, 353) 取模的结果即可。

    数据范围

    $ 1 le n le 10^5 $

    解题思路

    这题是一道熟练题,但也得熟练到一定程度才能很快的切掉吧

    attention 下面我用 (y_r) 代表 (frac 1y)

    subtask1:两棵树都给出

    很简单吧,直接 set 看看两棵树相同边集的大小

    答案就是

    [large y^{n-|E_1 cap E_2|} ]

    subtask2:给出第一棵树

    答案是

    [large sum_{E}[E_1 cap E_2=E]y^{n-|E|}(k-2)!sum_{sum deg_i=k-2} prod_{i=1}^kfrac{siz_i^{deg_i+1}}{deg_i!} ]

    前面枚举并集,然后 (prufer) 序列计数,每个联通块可以随便选出来一个点连边,(k) 指的是联通块个数 = (n-|E|)(siz) 指的是联通块大小

    继续

    [large sum_{E}[E_1 cap E_2=E]y^{n-|E|}(k-2)!sum_{sum deg_i=k-2} prod_{i=1}^kfrac{siz_i^{deg_i+1}}{deg_i!}\ large =sum_{E}[E_1 cap E_2=E]y^{n-|E|}(k-2)!left (prod_{i=1}^k siz_i ight)sum_{sum deg_i=k-2} prod_{i=1}^kfrac{siz_i^{deg_i}}{deg_i!}\ large =sum_{E}[E_1 cap E_2=E]y^{n-|E|}(k-2)!left (prod_{i=1}^k siz_i ight)left[x^{k-2} ight] prod_{i=1}^k e^{siz_ix}\ large =sum_{E}[E_1 cap E_2=E]y^{n-|E|}(k-2)!left (prod_{i=1}^k siz_i ight)left[x^{k-2} ight] e^{nx}\ large =sum_{E}[E_1 cap E_2=E]y^{n-|E|}left (prod_{i=1}^k siz_i ight)n^{k-2}\ ]

    其实直接从组合意义理解也是可以的,就是 (prufer) 序列其实填 (n) 个数中的任意一个都可以

    发现集合的并恰好等于很难办,设

    [large g(E)=[E_1 cap E_2=E]left (prod_{i=1}^k siz_i ight)n^{k-2}\ large f(E)=left (prod_{i=1}^k siz_i ight)n^{k-2}\ ]

    [large FakeAns=sum_E y^{n-|E|}f(E)\ large=sum_{E}g(E)sum_{E' subseteq E}y^{n-|E'|}\ large = sum_Eg(E)y^nsum_{k=0}^{|E|}{|E|choose k}y_r^{k}\ large = sum_Eg(E)y^n(y_r+1)^{|E|}\ ]

    这里给出另一种顺推做法

    [large f(E) = sum_{Esubseteq E'} g(E)\ large g(E)=sum_{E subseteq E'}(-1)^{|E'|-|E|}f(E')\ large sum_{E}y^{n-|E|}g(E)=sum_E y^{n-|E|}sum_{E subseteq E'}(-1)^{|E'|-|E|}f(E')\ large =sum_{E}f(E)sum_{k=0}^{|E|} {|E|choose k}(-1)^{|E|-k}y^{n-k}\ large =sum_{E}y^n(y_r-1)^{|E|}f(E) ]

    因此有

    [large Ans = sum_{E}y^{n-|E|}g(E) = sum_{E}y^n(y_r-1)^{|E|}f(E)\ large =sum_{E}y^n(y_r-1)^{|E|}left (prod_{i=1}^k siz_i ight)n^{k-2}\ large =y^{n}n^{n-2}sum_{E}left (prod_{i=1}^k siz_i ight)left(frac {y_r-1}{n} ight)^{|E|}\ ]

    考虑后面暴力 (dp)(dp[i][j]) 表示当前节点为 (i),当前联通块大小为 (j) 的方案数,走出去的时候把 (siz) 乘上即可

    用组合一样可以做到更优的 (Theta(n)) 做法,问题等价于把树分成若干的联通块,每个块中选出一个点的方案数,因此我们设 (dp[i][0/1]) 表示当前节点为 i,当前联通块有没有选择关键点即可

    具体的 (dp) 转移方程

    [large y = son~of~x\ large dp[x][0] = dp[x][0]cdot(dp[y][1]+frac {y_r-1}{n}dp[y][0])\ large dp[x][1] = dp[x][1] cdot(dp[y][1]+frac {y_r-1}{n}dp[y][0])+frac {y_r-1}{n}dp[x][0]cdot dp[y][1] ]

    subtask3:一棵树也不给

    由上一问可知

    [large Ans = sum_{E}f(E)^2y^n(y_r-1)^{|E|}\ large h(s) = sum_{|E| = s}f(E)^2\ large Ans = y^nsum_{i=0}h(i)(y_r-1)^{i} ]

    推法一:大力推式子,枚举有几个联通块

    [large h(s)=sum_{sum siz_i=n}frac {n!}{(n-s)!}prodfrac {siz_i^{siz_i-2}}{siz_i!}left(prod siz_i ight)^2 n^{2n-2s-4}\ large =sum_{sum siz_i=n}frac {n!}{(n-s)!}prodfrac {siz_i^{siz_i-2}}{siz_i!}left(prod siz_i ight)^2 n^{2n-2s-4}\ large =frac {n!}{(n-s)!}n^{2n-2s-4}sum_{sum siz_i=n}prodfrac {siz_i^{siz_i}}{siz_i!} \ large =frac {n!}{(n-s)!}n^{2n-2s-4}sum_{sum siz_i=n}prodfrac {siz_i^{siz_i}}{siz_i!} \ large =frac {n!}{(n-s)!}n^{2n-2s-4}[x^n](sum_{jge1}frac {j^j}{j!}x^j)^{n-s} \ large =frac {n!}{(n-s)!}n^{2n-2s-4}[x^n](sum_{jge1}frac {j^j}{j!}x^j)^{n-s} \ ]

    [large Ans = y^n(y_r-1)^nsum_{s=1}^n(y_r-1)^{-s}h(n-s)\ large Ans = frac {n!y^n(y_r-1)^n}{n^4}sum_{s=1}^nfrac {(y_r-1)^{-s}}{s!}n^{2s}[x^n]left(sum_{jge1}frac {j^j}{j!}x^j ight)^{s}\ large Ans = frac {n!y^n(y_r-1)^n}{n^4}sum_{s=1}^n[x^n]frac {left(frac{n^2}{y_r-1}sum_{jge1}frac {j^j}{j!}x^j ight)^{s}}{s!}\ large Ans = frac {n!y^n(y_r-1)^n}{n^4}[x^n]e^{left(frac{n^2}{y_r-1}sum_{jge1}frac {j^j}{j!}x^j ight)}\ ]

    这题到底要多熟练啊!

    推法二:生成函数熟练者使用

    [large Ans = frac{y^n}{n^4}(y_r-1)^{n}sum_{E}prod (siz_i^2n^2 (y_r-1)^{-1})\ ]

    下面是组合意义,可以从联通块的角度考虑,一个联通块对乘积的贡献是 (siz_i^2n^2(y_r-1)^{-1}),又因为一个联通块有 (siz_i^{siz_i-2}) 种生成树,所以总贡献是 (siz_i^{siz_i}n^2(y_r-1)^{-1})

    即其生成函数是

    [large F(x)=sum_{j ge 1} frac {n^2}{y_r-1}j^jx^j ]

    用若干联通子图合成总图方案数为

    [large G(x) = e^{F(x)} ]

    显然答案就是

    [large [x^n]G(x) ]

    代码在下

    #include <queue>
    #include <vector>
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define MP make_pair
    #define ll long long
    #define fi first
    #define se second
    using namespace std;
    
    template <typename T>
    void read(T &x) {
        x = 0; bool f = 0;
        char c = getchar();
        for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
        for (;isdigit(c);c=getchar()) x=x*10+(c^48);
        if (f) x=-x;
    }
    
    template<typename F>
    inline void write(F x, char ed = '
    ')
    {
    	static short st[30];short tp=0;
    	if(x<0) putchar('-'),x=-x;
    	do st[++tp]=x%10,x/=10; while(x);
    	while(tp) putchar('0'|st[tp--]);
    	putchar(ed);
    }
    
    template <typename T>
    inline void Mx(T &x, T y) { x < y && (x = y); }
    
    template <typename T>
    inline void Mn(T &x, T y) { x > y && (x = y); }
    
    const int P = 998244353;
    ll fpw(ll x, ll mi) {
    	ll res = 1;
    	for (; mi; mi >>= 1, x = x * x % P)
    		if (mi & 1) res = res * x % P;
    	return res; 
    } 
    
    #include <set> 
    ll n, y, op;
    namespace subtask0 {
    	void main() {
    		set<pair<int, int> > s;
    		for (int i = 1, x, y;i < n; i++) {
    			read(x), read(y); if (x > y) swap(x, y);
    			s.insert(MP(x, y));
    		}
    		int cnt = n;
    		for (int i = 1, x, y;i < n; i++) {
    			read(x), read(y); if (x > y) swap(x, y);
    			if (s.find(MP(x, y)) != s.end()) cnt--;
    		}
    		write(fpw(y, cnt));
    	}
    }
    
    namespace subtask1 {
    	
    	const int N = 200050;
    	int h[N], ne[N<<1], to[N<<1], tot;
    	inline void add(int x, int y) {
    		ne[++tot] = h[x], to[h[x] = tot] = y;
    	}
    	
    	ll dp[N][2], Y;
    	void dfs(int x, int fa) {
    		dp[x][0] = dp[x][1] = 1;
    		for (int i = h[x]; i; i = ne[i]) {
    			int y = to[i]; if (y == fa) continue;
    			dfs(y, x); ll t0 = dp[x][0], t1 = dp[x][1];
    			dp[x][0] = t0 * (dp[y][1] + Y * dp[y][0] % P) % P;
    			dp[x][1] = t1 * (dp[y][1] + Y * dp[y][0] % P) % P + Y * t0 % P * dp[y][1] % P;
    			dp[x][1] %= P;
    		}
    	}
    	void main() {
    		for (int i = 1, x, y;i < n; i++)
    			read(x), read(y), add(x, y), add(y, x); 
    		if (y == 1) return write(fpw(n, n - 2));
    		ll ans = fpw(y, n) * fpw(n, n - 2) % P;
    		Y = fpw(y, P - 2) - 1, Y = Y * fpw(n, P - 2) % P;
    		dfs(1, 0);
    		write(ans * dp[1][1] % P);
    	}
    }
    
    namespace subtask2 {
    	
    	const int G = 3;
    	const int N = 300500;
    	ll g[N], f[N], A[N], B[N], C[N], D[N], E[N];
    	int r[N], lim;
    	void NTT(ll *f, int ty) {
    		for (int i = 0;i < lim; i++)
    			if (r[i] > i) swap(f[i], f[r[i]]);
    		for (int i = 1;i < lim; i <<= 1) {
    			ll wn = fpw(G, (P - 1) / (i << 1));
    			for (int j = 0;j < lim; j += (i << 1)) {
    				ll w = 1;
    				for (int k = 0;k < i; k++, w = w * wn % P) {
    					ll x = f[j+k], y = f[i+j+k] * w % P;
    					f[j+k] = (x + y) % P;
    					f[i+j+k] = (x - y + P) % P;
    				}
    			}
    		}
    		if (ty == -1) {
    			ll inv = fpw(lim, P - 2);
    			for (int i = 0;i < lim; i++) f[i] = f[i] * inv % P;
    			reverse(f + 1, f + lim);
    		}
    	}
    	
    	void Inv(int deg, ll *a, ll *b) {
    		b[0] = fpw(a[0], P - 2); int len;
    		for (len = 2;len < (deg << 1); len <<= 1) {
    			lim = len << 1;
    			for (int i = 0;i < len; i++) A[i] = a[i], B[i] = b[i];
    			for (int i = 0;i < lim; i++)
    				r[i] = (r[i>>1]>>1) | ((i & 1) ? len : 0);
    			NTT(A, 0), NTT(B, 0);
    			for (int i = 0;i < lim; i++)
    				b[i] = (2 - A[i] * B[i] % P + P) * B[i] % P;
    			NTT(b, -1);
    			for (int i = len;i < lim; i++) b[i] = 0;
    		}
    		for (int i = 0;i < len; i++) A[i] = B[i] = 0;
    	}
    	
    	ll inv[N];
    	void init(void) {
    		inv[0] = inv[1] = 1;
    		for (int i = 2;i <= n * 2; i++)
    			inv[i] = inv[P % i] * (P - P / i) % P;
    	}
    	void door(ll *a, int deg) {
    		for (int i = 1;i < deg; i++)
    			a[i-1] = a[i] * i % P;
    		a[deg - 1] = 0;
    	}
    	void chick(ll *a, int deg) {
    		for (int i = deg - 2;i >= 0; i--)
    			a[i + 1] = a[i] * inv[i + 1] % P;
    		a[0] = 0;
    	}
    	void Ln(int deg, ll *a) {
    		Inv(deg, a, C), door(a, deg);
    		NTT(a, 1), NTT(C, 1);
    		for (int i = 0;i < lim; i++)
    			a[i] = a[i] * C[i] % P;
    		NTT(a, -1), chick(a, deg); 
    	}
    	
    	void Exp(int deg, ll *a, ll *b) {
    		int len; b[0] = 1;
    		for (len = 2;len < (deg << 1); len <<= 1) {
    			lim = len << 1;
    			for (int i = 0;i < len; i++) E[i] = b[i];
    			Ln(len, E), E[0] = (a[0] + 1 - E[0] + P) % P; 
    			for (int i = 1;i < len; i++)
    				E[i] = (a[i] - E[i] + P) % P;
    			NTT(E, 1), NTT(b, 1);
    			for (int i = 0;i < lim; i++)
    				b[i] = b[i] * E[i] % P;
    			NTT(b, -1);
    			for (int i = len;i < lim; i++) b[i] = 0;
    		}
    	}
    	
    	void main() {
    		if (y == 1) return write(fpw(n, 2 * n - 4));
                    ll ans = 1, Y = fpw(y, P - 2) - 1; init();
    		for (int i = 1;i <= n; i++)
    			ans = ans * i % P * y % P * Y % P;
    		ans = ans * fpw(n * n % P * n % P * n % P, P - 2) % P;
    		ll tmp = n * n % P * fpw(Y, P - 2) % P, jie = 1;
    		for (int i = 1;i <= n; i++) {
    			jie = jie * inv[i] % P;
    			f[i] = tmp * fpw(i, i) % P * jie % P;
    //			write(f[i], ' ');
    		}
    //		puts("");
    		Exp(n + 1, f, g), write(g[n] * ans % P);
    //		for (int i = 0;i <= n; i++)
    //			write(g[i], ' ');
    	}
    }
    
    int main() {
    	read(n), read(y), read(op);
    	if (op == 0) subtask0::main();
    	else if (op == 1) subtask1::main();
    	else if (op == 2) subtask2::main();
    	return 0;
    }
    
  • 相关阅读:
    点击子窗体给父窗体上的对象赋值
    框架使用及规范参考
    像Google日历一样的日程管理
    TreeView 和 Menu 的用法
    甘特图-svg版 支持客户端事件
    js获取DropDownList的选择项
    GridView,Repeater分页控件:WebPager(开源)
    TextBox 禁止客户端输入 前台通过JS赋值 并在后台获取
    对象实体 参考标准
    以编程方式控制ScriptManager
  • 原文地址:https://www.cnblogs.com/Hs-black/p/13369607.html
Copyright © 2020-2023  润新知