[WC2019] 数树

传送门

综合性非常强的一道题。。。

给定两棵树

发现题目其实是 y 的两颗树的边集交集形成的森林的连通块数次方,map 搞一搞就行。

给定一棵树

考虑枚举两个边集的交集,根据森林的连通块数可表达为点减边,可知答案为 i=0n1giyni,其中 gi 表示交集边数为 i第二棵树树的数量,发现 gi 不好直接计算,是因为我们无法保证这 i 条边之外的边不在交集中,所以我们只能算出至少,再通过别的方法得到恰好。这里有两种解决方法:

第一种:考虑 fi 表示钦定 i 条边属于交集之后第二棵树的数量,显然有:

fi=j=in1(ji)gj

根据二项式反演得:

gi=j=in1(1)ji(ji)fj

那么答案就是:

Ans=i=0n1ynij=in1(1)ji(ji)fj=ynj=0n1fji=0j(ji)(1)ji(y1)i=ynj=0n1fj(y11)j

第二种:上面的式子无法使用普通的容斥,是因为有一个 yni 在。我们首先简单化一化式子:

i=0n1giyni=yni=0n1gi(y1)i

考虑 (y1)i=(ij)(y11)j,我们直接去算至少,一个钦定了 j 条边的方案我们让它算 (y11)j 的贡献,最后再乘回 yn 就是正确答案了。

Ans=ynj=0n1fj(y11)j

其实两种方法最后的式子都是一样的。

考虑计算 fi,对于一种选边的方案,最后可生成的树的数量可以由 prufer 序列得到,即 nni2szej=nn2niszei,其实现在已经可以树形 DP 了,我们只需要对于选边的方案进行 DP,每选一条边额外乘上 n1(y11) 就可以了。对于 szej,朴素的想法是记一下现在点所在的连通块大小,但是 szej 相当于在每个连通块选一个点的方案,所以我们只需用记 fu,0/1 表示做到 u,目前连通块是否选了的答案,时间复杂度 O(n)

不给定树

首先还是枚举交集,然后像上面那样处理成至少,答案就是:

Ans=ynj=0n1fj(y11)j

注意:这里 fi 的定义变成了钦定 i 条边后,两棵树的方案数。

这里没有树形态的限制,考虑直接写出 fi 的通项:

fs=ai=nn!(ns)!i=1nsaiai2ai!(nns2i=1nsai)2

解释一下,一开始先枚举每个连通块大小,n! 把点放进去,但是连通块之间无序,所以除以 (ns)!aiai2 是连通块内树的方案,同时连通块内点无序,除以 ai!,最后是剩下的生成两棵树的方案。

我们把这个带回去,设 C=(y11)

Ans=yns=0n1Csai=nn!(ns)!i=1nsaiai2ai!(nns2i=1nsai)2=ynn!n4s=0n1Csn2n2s(ns)!ai=naiaiai!=ynn!n4s=0n1Cnsn2ss!ai=naiaiai!

既然有 ai=n,不妨把后面的东西写成卷积的形式。

Ans=ynn!n4s=0n1Cnsn2ss![xn](j1jjj!xj)s=ynn!Cnn4[xn]s=0n1(j1n2jjCj!xj)ss!=ynn!Cnn4[xn]ej1n2jjCj!xj

所以只需要做一遍多项式 EXP 就可以解决此题啦。

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<map>
#include<vector>
#define N 100005
#define M 530000
#define mo 998244353
#define int long long
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
inline int qpow(int x,int b){
	int res=1;
	for(;b;x=x*x%mo,b>>=1) if(b&1) res=res*x%mo;
	return res;
}
int n,y,op;
namespace sub0{
	int fa[N];
	map<pair<int,int>,int> MP;
	int find(int x){
		return x==fa[x]?x:(fa[x]=find(fa[x]));
	}
	inline void merge(int x,int y){
		x=find(x),y=find(y);
		if(x==y) return;
		fa[x]=y;
	}
	void main(){
		for(int i=1;i<=n;++i) fa[i]=i;
		for(int i=1;i<n;++i){
			int x=read(),y=read();
			if(x>y) swap(x,y);
			MP[{x,y}]++;
		}
		for(int i=1;i<n;++i){
			int x=read(),y=read();
			if(x>y) swap(x,y);
			if(MP[{x,y}]) merge(x,y);
		}
		int ans=1;
		for(int i=1;i<=n;++i){
			if(i==find(i)) ans=ans*y%mo;
		}
		printf("%lld\n",ans);
	}
}
namespace sub1{
	int f[N][2],C;
	vector<int> G[N];
	void dfs(int u,int fa){
		f[u][0]=f[u][1]=1;
		for(int v:G[u]){
			if(v==fa) continue;
			dfs(v,u);
			int tmp0=f[u][0],tmp1=f[u][1];
			f[u][0]=(f[v][1]*tmp0%mo+tmp0*f[v][0]%mo*C%mo)%mo;
			f[u][1]=(f[v][1]*tmp1%mo+tmp0*f[v][1]%mo*C%mo+tmp1*f[v][0]%mo*C%mo)%mo;
		}
	}
	void main(){
		for(int i=1;i<n;++i){
			int x=read(),y=read();
			G[x].push_back(y);
			G[y].push_back(x);
		}
		C=(qpow(y,mo-2)-1)*qpow(n,mo-2)%mo;
		dfs(1,0);
		printf("%lld",f[1][1]*qpow(y,n)%mo*qpow(n,n-2)%mo);
	}
}
namespace sub2{
	inline void clear(int *A,int x,int y){for(int i=x;i<y;++i) A[i]=0;}
	inline void copy(int *A,int *B,int x,int y){for(int i=x;i<y;++i) A[i]=B[i];}
	inline void mul(int *A,int *B,int x,int y){for(int i=x;i<y;++i) A[i]=A[i]*B[i]%mo;}
	int cir[M],rt;
	inline void NTT(int *f,int len,int tag){
		if(len!=rt) for(int i=0;i<len;++i) cir[i]=(cir[i>>1]>>1)|((i&1)?len>>1:0);
		rt=len;
		for(int i=0;i<len;++i) if(i<cir[i]) swap(f[i],f[cir[i]]);
		for(int l=2;l<=len;l<<=1){
			int sze=l>>1,buf=qpow(tag?3:332748118,(mo-1)/l);
			for(int i=0;i<len;i+=l){
				int bas=1;
				for(int j=i;j<i+sze;++j,bas=bas*buf%mo){
					int tmp=bas*f[j+sze]%mo;
					f[j+sze]=(f[j]-tmp+mo)%mo,(f[j]+=tmp)%=mo;
				}
			}
		}
		if(tag==1) return;
		int invn=qpow(len,mo-2);
		for(int i=0;i<len;++i) f[i]=f[i]*invn%mo;
	}
	int tmul[M];
	inline void polyMul(int *A,int *B,int len1,int len2){
		#define sav tmul
		int len=1;
		while(len<(len1<<1)) len<<=1;
		copy(sav,B,0,len);
		NTT(A,len,1),NTT(sav,len,1),mul(A,sav,0,len),NTT(A,len,0);
		clear(A,len2,len),clear(sav,0,len);
		#undef sav
	}
	int inv1[M],inv2[M],inv3[M];
	inline void polyInv(int *A,int len){
		#define sav1 inv1
		#define sav2 inv2
		#define sav3 inv3
		int lim=1;while(lim<len) lim<<=1;
		sav1[0]=qpow(A[0],mo-2);
		for(int l=2;l<=lim;l<<=1){
			int r=l<<1;copy(sav3,A,0,l);
			for(int i=0;i<(l>>1);++i) sav2[i]=(sav1[i]<<1)%mo;
			NTT(sav1,r,1),mul(sav1,sav1,0,r);
			NTT(sav3,r,1),mul(sav1,sav3,0,r);
			NTT(sav1,r,0),clear(sav1,l,r);
			for(int i=0;i<l;++i) sav1[i]=(sav2[i]-sav1[i]+mo)%mo;
		}
		copy(A,sav1,0,len);
		clear(sav1,0,lim<<1),clear(sav2,0,lim<<1),clear(sav3,0,lim<<1);
		#undef sav1
		#undef sav2
		#undef sav3
	}
	inline void polyDer(int *A,int len){
		for(int i=1;i<len;++i) A[i-1]=A[i]*i%mo;A[len-1]=0;
	}
	inline void polyInt(int *A,int len){
		for(int i=len-1;i>=1;--i) A[i]=A[i-1]*qpow(i,mo-2)%mo;A[0]=0;
	}
	int ln1[M];
	inline void polyLn(int *A,int len){
		#define sav ln1
		copy(sav,A,0,len),polyDer(A,len),polyInv(sav,len);
		polyMul(A,sav,len,len),polyInt(A,len),clear(sav,0,len);
		#undef sav
	}
	int exp1[M],exp2[M];
	inline void polyExp(int *A,int len){
		#define sav1 exp1
		#define sav2 exp2
		int lim=1;while(lim<len) lim<<=1;
		sav1[0]=1;
		for(int l=2;l<=lim;l<<=1){
			copy(sav2,sav1,0,l>>1),polyLn(sav2,l);
			for(int i=0;i<l;++i) sav2[i]=(A[i]-sav2[i]+mo)%mo;
			sav2[0]=(sav2[0]+1)%mo;
			polyMul(sav1,sav2,l,l);
		}
		copy(A,sav1,0,len),clear(sav1,0,lim),clear(sav2,0,lim);
		#undef sav1
		#undef sav2
	}
	int fac[N],ifac[N],F[M];
	void main(){
		if(y==1) return (void)(printf("%lld",qpow(n,2*(n-2))));
		int ny=qpow(y,mo-2)-1,iny=qpow(ny,mo-2);
		fac[0]=1;for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%mo;
		ifac[n]=qpow(fac[n],mo-2);
		for(int i=n;i>=1;--i) ifac[i-1]=ifac[i]*i%mo;
		for(int i=1;i<=n;++i) F[i]=n*n%mo*iny%mo*qpow(i,i)%mo*ifac[i]%mo;
		polyExp(F,n+1);
		printf("%lld\n",fac[n]*qpow(ny,n)%mo*qpow(n,mo-5)%mo*F[n]%mo*qpow(y,n)%mo);
	}
}
signed main(){
	n=read(),y=read(),op=read();
	if(op==0) sub0::main();
	if(op==1) sub1::main();
	if(op==2) sub2::main();
	return 0;
}

posted @   CHiSwsz  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示