WC2019数树

  • 题目链接:WC2019数树
  • 题意简述:直接看链接吧,简述不来.
  • \(solution\)就是直接推式子,重点讲\(Sub1\)
  • \(Sub1\):设红树边集为\(E\).

\[\begin{aligned} ans &= \sum_S y^{n-|S\cap E|} \\ &= y^n\sum_S y^{-|S|} * \sum_T [T \cap E == S]\\ &令g(S) = \sum_T[T \cap E == S].考虑组合意义,即与E的边集恰好为S.考虑容斥,设f(S)为强制与E的边集为S,且使图生成树的方案.那么\\ &f(S) = \sum_{S \subseteq T} g(T)\\ &g(S) = \sum_{S \subseteq T} f(T) * (-1)^{|T| - |S|}\\ ans &= y^n *\sum_{S}y^{-|S|}\sum_{S \subseteq T}f(T) * (-1)^{|T| - |S|}\\ &考虑交换求和顺序,用二项式定理\\ &= y^n * \sum_T f(T) * (y^{-1}-1)^{|T|}\\ &考虑f(T)即将n - T个连通块生成树的方案数.所以\\ &= y^n * \frac{n^n}{n^2} * \sum_{T} * (\prod_{i = 1}^{n-|T|} * s_i) * (\frac{y^{-1}-1}{n})^{|T|}\\ &= \frac{(1-y)^n}{n^2}*\sum_{T \subseteq E}(\prod_{i = 1}^{n-|T|}(s_i*n*\frac{n}{y^{-1}-1}))\\ &令K = s_i*n*\frac{n}{y^{-1}-1}.考虑组合意义.意即将树划分为若干个联通块.\\ &每个连通块有可以选一个点乘以K.所以设dp_{u,0|1}表示当前dp到u的子树,该连通块选了|没选了一个点乘K的贡献.转移分这条边割与不割转移即可\\ &时间复杂度O(n) \end{aligned}\]

  • \(Sub2:\)
  • 这一部分是比较显然的.把上面的式子多增加一个枚举E,然后演变乘算一个森林若干个贡献乘积.设出树的生成函数,对其做\(exp\)即可得到森林的生成函数的答案.

\(Tag:组合数学,NTT,生成函数,图计数\)

  • 启示:推式子的时候不要一口气推的太跳步,容易推不出来,在草稿纸上一步步推,一步步观察.
  • 代码如下
/*WC2019数树*/ 
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
	char c = getchar();
	int x = 0;
	while(c < '0' || c > '9')		c = getchar();
	while(c >= '0' && c <= '9')		x = x * 10 + c - 48,c = getchar();
	return x;
}
const int N = 4e5 + 10;
typedef pair<int,int> pii;
#define mod 998244353
int qpow(int x,int y){
	if(y < 0)	return 1;
	int ans = 1;
	while(y){
		if(y & 1)	ans = 1ll * ans * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return ans;
}
int n,y,op;
void add(int &x,int y){
	x += y - mod;
	x += (x >> 31) & mod;
}
namespace Sub0{
	pii e[N];
	map<pii,bool>h;
	#define mp make_pair
	void solve(){
		int ans = n;
		for(int i = 1; i < n; ++i){
			int u = read(),v = read();
			h[mp(u,v)] = h[mp(v,u)] = 1;
		}
		for(int i = 1; i < n; ++i){
			int u = read(),v = read();
			if(h[mp(u,v)]){
				ans--;
			}
		}
		ans = qpow(y,ans);
		printf("%d\n",ans);
	}
}
int G = 3,Gi;
namespace Poly{
	int rk[N],g[N],A[N],B[N];
	void getrk(int lim,int l){
		for(int i = 1; i < lim; ++i)	rk[i] = (rk[i>>1] >> 1) | ((i & 1) << (l - 1));
	}
	void ntt(int *f,int lim,int type){
		int n = 1,l = 0;
		while(n < lim)	n <<= 1,l++;
		getrk(n,l);
		for(int i = 0; i < lim; ++i)	g[i] = f[rk[i]];
		for(int i = 0; i < lim; ++i)	f[i] = g[i];
		for(int j = 1; j < lim; j <<= 1){
			int Wn = qpow(type == 1?G:Gi,(mod - 1) / (j << 1));
			for(int i = 0,len = j << 1; i < lim; i += len){
				int w = 1;
				for(int k = 0; k < j; ++k,w = 1ll * w * Wn % mod){
					int x = f[i+k],y = 1ll * w * f[i+k+j] % mod;
					f[i+k] = (x + y) % mod;
					f[i+k+j] = (x - y + mod) % mod; 
				}
			}
		}
		if(type != 1){
			int inv = qpow(lim,mod-2);
			for(int i = 0; i < lim; ++i)	f[i] = 1ll * f[i] * inv % mod;
		}
	}
	void exp(int *f,int *g,int l,int r){
		int n = r - l;
		if(n == 1){
			if(l != 0)	f[l] = 1ll * f[l] * qpow(l,mod-2) % mod;
			return;
		}
		int mid = (l + r) >> 1;
		exp(f,g,l,mid);
		for(int i = l; i < mid; ++i)	A[i-l] = f[i];
		for(int i = mid; i < r; ++i)	A[i-l] = 0;
		for(int i = 0; i < n; ++i)		B[i] = 1ll * g[i] * i % mod;
		ntt(A,n,1);ntt(B,n,1);
		for(int i = 0; i < n; ++i)	A[i] = 1ll * A[i] * B[i] % mod;
		ntt(A,n,-1);
		for(int i = mid; i < r; ++i) 	add(f[i],A[i-l]);
		exp(f,g,mid,r);
	}
}
using Poly::getrk;
using Poly::ntt;
using Poly::exp;
int fac[N<<1],invfac[N<<1];
int C(int x,int y){
	return 1ll * fac[x] * invfac[y] % mod * invfac[x-y] % mod;
}
namespace Sub1{
	int dp[N][2],K;
	struct Edge{
		int nxt,point;
	}edge[N<<1];
	int head[N],tot;
	void add_edge(int u,int v){
		edge[++tot].nxt = head[u];
		edge[tot].point = v;
		head[u] = tot;
	}
	void treedp(int u,int fa){
		dp[u][0] = 1;dp[u][1] = K;
		for(int i = head[u]; i ;i = edge[i].nxt){
			int v = edge[i].point;
			if(v ^ fa){
				treedp(v,u);
				int f0 = (1ll * dp[u][0] * dp[v][0] + 1ll * dp[u][0] * dp[v][1]) % mod;
				int f1 = (1ll * dp[u][0] * dp[v][1] + 1ll * dp[u][1] * dp[v][0]) % mod;
				add(f1,1ll * dp[u][1] * dp[v][1] % mod);
				dp[u][0] = f0;
				dp[u][1] = f1;
			}
		}
	}
	void solve(){
		int v = (1 - y + mod) % mod;
		v = qpow(v,n);
		int inv = 1ll * n * n % mod;
		inv = qpow(inv,mod-2);
		v = 1ll * v * inv % mod;
		K = 1ll * qpow((qpow(y,mod-2) - 1),mod-2) * n % mod;
		for(int i = 1; i < n; ++i){
			int u = read(),v = read();
			add_edge(u,v);add_edge(v,u);
		}
		treedp(1,0);
		int ans = 1ll * v * dp[1][1] % mod;
		printf("%d\n",ans);
	}
}
namespace Sub2{
	int f[N],g[N];
	void solve(){
		int lim = 1;
		Gi = qpow(G,mod-2);
		while(lim <= n)	lim <<= 1;
		fac[0] = 1;
		for(int i = 1; i < lim; ++i)	fac[i] = 1ll * fac[i-1] * i % mod;
		for(int i = 0; i < lim; ++i)	invfac[i] = qpow(fac[i],mod-2);
		int sn = 1ll * n * n % mod;
		int inv = qpow(qpow(y,mod-2) - 1,mod - 2);
		for(int i = 1; i < lim; ++i)
			f[i] = 1ll * qpow(i,i) * sn % mod * inv % mod * invfac[i] % mod;
		g[0] = 1;
		exp(g,f,0,lim);		
		int ans = 1ll * g[n] * fac[n] % mod * qpow((1 - y + mod),n) % mod;
		int invn = qpow(qpow(n,4),mod-2);
		ans = 1ll * ans * invn % mod;
		printf("%d\n",ans);
	}
}
int main(){
	n = read(),y = read(),op = read();
	if(y == 1){
		if(op == 0){
			puts("1");
		}
		else if(op == 1){
			printf("%d\n",qpow(n,n-2));
		}
		else{
			printf("%lld\n",1ll * qpow(n,n-2)*qpow(n,n-2)%mod);
		}
		return 0;
	}
	if(op == 0){
		Sub0::solve();
		return 0;
	}
	if(op == 1){
		Sub1::solve();
		return 0;
	}
	if(op == 2){
		Sub2::solve();
	}
	return 0;
}
posted @ 2021-03-13 08:33  y_dove  阅读(126)  评论(0编辑  收藏  举报