【WC2019】数树(容斥原理)(生成函数)(树形DP)(多项式Exp)(数数神题)

传送门


没弄明白前觉得毒瘤,弄明白之后,这TM就是数数神题啊。

然而我这道题将近一半的代码都在写多项式全家桶。。。

这道题的推导过程确实有点繁琐,但是没有办法啊,毕竟性质就是这么复杂啊。

白云和白兔(laofu出题用的NPC),老虎和蒜头(whzzt出题用的NPC),真心有毒真的牛逼啊。。。

题解:

首先简单分析一下题目的那个限制:有公共路径的点颜色必须一样。

很容易发现其实就是两棵树里面的公共边所连的点颜色要一样,这个具有传递性。

简要题意:
两棵大小为 n n n的有标号无根树 T 1 , T 2 T_1,T_2 T1,T2,每棵树上的节点标号都是 1 − n 1-n 1n,你需要对这 2 n 2n 2n个点染色,如果边 < u , v > <u,v>同时存在于两棵树中,它们必须被染相同的颜色,求染色方案数。
你需要对于三种情况回答。

  1. 给定 T 1 , T 2 T_1,T_2 T1,T2,该情况下的答案。
  2. 给定 T 1 T_1 T1 T 2 T_2 T2取遍所有 n n − 2 n^{n-2} nn2种有标号无根树的答案之和。
  3. T 1 , T 2 T_1,T_2 T1,T2分别取遍所有 n n − 2 n^{n-2} nn2种有标号无根树,一共 n 2 n − 4 n^{2n-4} n2n4种情况的答案之和。

一般来说,树是由一个边的集合和一个点的集合组成的集合 T = { V , E } T=\{V,E\} T={V,E},以下的讨论为了方便将树表示为边的集合 T 1 = E 1 , T 2 = E 2 T_1=E_1,T_2=E_2 T1=E1,T2=E2

则一个树对 < T 1 , T 2 > <T1,T2>的答案为 y n − ∣ T 1 ∩ T 2 ∣ y^{n-|T_1\cap T_2|} ynT1T2,为了方便,我们把 y n y^n yn丢掉,最后乘进去,以下提到的一个树对的答案均是指 y − ∣ T 1 ∩ T 2 ∣ y^{-|T_1\cap T_2|} yT1T2

Subtask 1:

直接开个set或者map或者用什么你喜欢的方式统计一下重复出现的边的个数即可。

Subtask 2:

没有勇气的同学建议干一杯伏特加再来看

考虑其中一棵树是已经确定了的。

Q ( S ) Q(S) Q(S)表示与 T 1 T_1 T1的交为 S S S T 2 T_2 T2的数量。
C ( S ) C(S) C(S)表示与 T 1 T_1 T1的交为 S S S的超集的 T 2 T_2 T2的数量。

根据定义 Q ( S ) = ∑ T 2 [ T 1 ∩ T 2 = S ] C ( S ) = ∑ T 2 [ S ⊆ T 1 ∩ T 2 ] Q(S)=\sum_{T_2}[T_1\cap T_2=S]\\C(S)=\sum_{T_2}[S\subseteq T_1\cap T_2] Q(S)=T2[T1T2=S]C(S)=T2[ST1T2]

容易注意到当 S ⊄ T 1 S\not\subset T_1 ST1 Q ( S ) = C ( S ) = 0 Q(S)=C(S)=0 Q(S)=C(S)=0
且有 C ( S ) = ∑ S ⊆ T Q ( T ) C(S)=\sum_{S\subseteq T}Q(T) C(S)=STQ(T)

根据FWT可以直接得到 Q ( S ) = ∑ S ⊆ T ( − 1 ) ∣ T ∣ − ∣ S ∣ C ( T ) Q(S)=\sum_{S\subseteq T}(-1)^{|T|-|S|}C(T) Q(S)=ST(1)TSC(T)

将答案写出来并考虑化简:

A n s = ∑ S ⊆ T 1 Q ( S ) y − ∣ S ∣ = ∑ S ⊆ T 1 y − ∣ S ∣ ∑ S ⊆ W ⊆ T 1 ( − 1 ) ∣ W ∣ − ∣ S ∣ C ( W ) = ∑ W ⊆ T 1 C ( W ) ( − 1 ) ∣ W ∣ ∑ S ⊆ W ( − y ) − ∣ S ∣ = ∑ W ⊆ T 1 C ( W ) ( − 1 ) ∣ W ∣ ∑ i = 0 ∣ W ∣ ( − y ) − i ( ∣ W ∣ i ) = ∑ W ⊆ T 1 C ( W ) ( − 1 ) ∣ W ∣ ( 1 − 1 y ) ∣ W ∣ = ∑ W ⊆ T 1 ( 1 y − 1 ) ∣ W ∣ C ( W ) \begin{aligned} Ans&=\sum_{S\subseteq T_1}Q(S)y^{-|S|}\\ &=\sum_{S\subseteq T_1}y^{-|S|}\sum_{S\subseteq W\subseteq T_1}(-1)^{|W|-|S|}C(W)\\ &=\sum_{W\subseteq T_1}C(W)(-1)^{|W|}\sum_{S\subseteq W}(-y)^{-|S|}\\ &=\sum_{W\subseteq T_1}C(W)(-1)^{|W|}\sum_{i=0}^{|W|}(-y)^{-i}{|W|\choose i}\\ &=\sum_{W\subseteq T_1}C(W)(-1)^{|W|}(1-\frac{1}{y})^{|W|}\\ &=\sum_{W\subseteq T_1}(\frac{1}{y}-1)^{|W|}C(W) \end{aligned} Ans=ST1Q(S)yS=ST1ySSWT1(1)WSC(W)=WT1C(W)(1)WSW(y)S=WT1C(W)(1)Wi=0W(y)i(iW)=WT1C(W)(1)W(1y1)W=WT1(y11)WC(W)

其实在 W ⊆ T 1 W\subseteq T_1 WT1的情况下, C ( W ) C(W) C(W)可以换一个表述方式:有 W W W这个边集中所有边的树的个数。

令人惊讶的是,此时 C ( W ) C(W) C(W)有一个简洁的计算式,假设仅保留这个边集中的边得到的是 k k k个连通块,大小分别为 a 1 , a 2 , ⋯ a k a_1,a_2,\cdots a_k a1,a2,ak,则在这些连通块之间加边成树的方案数是 n k − 2 ∏ i = 1 k a i n^{k-2}\prod_{i=1}^{k}a_i nk2i=1kai

证明的话,请移步这篇博客:here

来我们回到原来的式子继续推。

为了方便你不用往回翻我给你复制过来了,我们刚才已经得到的式子是:

A n s = ∑ W ⊆ T 1 ( 1 y − 1 ) ∣ W ∣ C ( W ) Ans=\sum_{W\subseteq T_1}(\frac{1}{y}-1)^{|W|}C(W) Ans=WT1(y11)WC(W)

以及保留 W W W中的边,得到 k = n − ∣ W ∣ k=n-|W| k=nW个连通块,大小分别为 a 1 , a 2 , … a k a_1,a_2,\dots a_k a1,a2,ak,连边成树的方案数为 n k − 2 ∏ i = 1 k a i n^{k-2}\prod_{i=1}^ka_i nk2i=1kai

C ( W ) C(W) C(W)展开得到:

A n s = ∑ W ⊆ T 1 ( 1 y − 1 ) ∣ W ∣ n n − ∣ W ∣ − 2 ∏ i = 1 n − ∣ W ∣ a i = ( 1 y − 1 ) n n 2 ∑ W ⊆ T 1 ∏ i = 1 n − ∣ W ∣ a i n 1 y − 1 \begin{aligned} Ans&=\sum_{W\subseteq T_1}(\frac{1}{y}-1)^{|W|}n^{n-|W|-2}\prod_{i=1}^{n-|W|}a_i\\ &=\frac{(\frac{1}y-1)^n}{n^2}\sum_{W\subseteq T_1}\prod_{i=1}^{n-|W|}\frac{a_in}{\frac{1}y-1} \end{aligned} Ans=WT1(y11)WnnW2i=1nWai=n2(y11)nWT1i=1nWy11ain

前面提出来的常数丢掉不管,设 k = n 1 y − 1 k=\dfrac{n}{\frac{1}y-1} k=y11n,考虑我们枚举 W W W的时候实际上枚举的就是一种树上连通块的划分。

定义一个大小为 a a a的连通块权值为 k a ka ka,一个划分的权值为所有连通块的权值之积,则现在的问题就是给你 T 1 T_1 T1,请你求出所有划分的权值之和。

很容易想到树上背包,设 f [ u ] [ i ] f[u][i] f[u][i]表示以 u u u为最高点的连通块,大小为 i i i的时候的已有贡献之和。转移考虑是否断父亲边,是否成一个新的连通块即可。

1 1 1为根,则最后的答案就是 k ∑ i = 1 n f [ 1 ] [ i ] ∗ i k\sum\limits_{i=1}^nf[1][i]*i ki=1nf[1][i]i

但是很遗憾,朴素的树上背包复杂度是 O ( n 2 ) O(n^2) O(n2)的过不了。

注意到答案式子里面第 i i i项有一个 ∗ i *i i,这是求导后求 1 1 1的点值的形式,考虑写成生成函数的形式然后求导。

则转移式子其实就很显然了:

F u ( x ) = F u ( x ) ∗ ( k F v ′ ( 1 ) + F v ( x ) ) F_u(x)=F_u(x)*(kF_v'(1)+F_v(x)) Fu(x)=Fu(x)(kFv(1)+Fv(x))

g u = k F u ′ ( 1 ) , f u = F u ( 1 ) g_u=kF_u'(1),f_u=F_u(1) gu=kFu(1),fu=Fu(1),则我们希望把 g u g_u gu凑出来,上面的式子两边求导后求 1 1 1的点值再乘上 k k k即可得到:

g u = g u ( g v + f v ) + f u g v g_u=g_u(g_v+f_v)+f_ug_v gu=gu(gv+fv)+fugv

而直接对原来的转移式子求点值可以得到:

f u = f u ∗ ( g v + f v ) f_u=f_u*(g_v+f_v) fu=fu(gv+fv)

转移复杂度变成 O ( 1 ) O(1) O(1),直接做。

Subtask 3 :

此时设 C ( W ) C(W) C(W)表示边集为 W W W的超集的树的个数,上一个子任务的容斥的直接原封不动推一遍得到:

A n s = ∑ W ( 1 y − 1 ) ∣ W ∣ C 2 ( W ) Ans=\sum_{W}(\frac{1}y-1)^|W|C^2(W) Ans=W(y11)WC2(W)

同样,将 C 2 ( W ) C^2(W) C2(W)暴力展开。

A n s = ∑ W ( 1 y − 1 ) ∣ W ∣ ( n n − ∣ W ∣ − 2 ∏ i = 1 n − ∣ W ∣ a i ) 2 = ∑ W ( 1 y − 1 ) ∣ W ∣ n 2 n − 2 ∣ W ∣ − 4 ∏ i = 1 n − ∣ W ∣ a i 2 = ( 1 y − 1 ) n n 4 ∑ W ∏ i = 1 n − ∣ W ∣ a i 2 n 2 1 y − 1 \begin{aligned} Ans&=\sum_{W}(\frac{1}y-1)^{|W|}(n^{n-|W|-2}\prod_{i=1}^{n-|W|}a_i)^2\\ &=\sum_{W}(\frac{1}y-1)^{|W|}n^{2n-2|W|-4}\prod_{i=1}^{n-|W|}a_i^2\\ &=\frac{(\frac{1}y-1)^n}{n^4}\sum_{W}\prod_{i=1}^{n-|W|}\frac{a_i^2n^2}{\frac{1}y-1} \end{aligned} Ans=W(y11)W(nnW2i=1nWai)2=W(y11)Wn2n2W4i=1nWai2=n4(y11)nWi=1nWy11ai2n2

同样,将前面的系数扔掉,考虑枚举 W W W本质上可以认为是在枚举森林。

k = n 2 1 y − 1 k=\dfrac{n^2}{\frac{1}y-1} k=y11n2,则问题变成了:一棵大小为 a a a的树的权值为 a 2 k a^2k a2k,一个森林的权值是所有树的权值之积,求所有 n n n个点的带标号森林的权值。

由于带标号,是一个非常显然的 e x p exp exp。树的情况,也就是连通情况的生成函数为

F ( x ) = ∑ i k i i x i i ! F(x)=\sum_{i}ki^i\frac{x^i}{i!} F(x)=ikiii!xi

e x p exp exp之后取第 n n n项即可。


代码:

#include
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;
typedef std::pair<int,int> pii;
#define fi first
#define se secnod

cs int mod=998244353;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline int dec(int a,int b){a-=b;return a+(a>>31&mod);}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int po(int a,int b){
	int r=1;for(;b;b>>=1,a=mul(a,a))
	if(b&1)r=mul(r,a);return r;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return ;}ex_gcd(b,a%b,y,x);y-=a/b*x;
}
inline int inv(int a){
	int x,y;ex_gcd(mod,a,y,x);
	return x+(x>>31&mod);
}
inline int sqr(int a){return mul(a,a);}

int n,y,op;
namespace T0{
	std::set<pii> s;
	void main(){
		for(int re i=1;i<n;++i){
			int u=gi(),v=gi();
			if(u>v)std::swap(u,v);
			s.insert(pii(u,v));
		}int tot=0;
		for(int re i=1;i<n;++i){
			int u=gi(),v=gi();
			if(u>v)std::swap(u,v);
			if(s.find(pii(u,v))!=s.end())++tot;
		}
		cout<<po(y,n-tot)<<"\n";
	}
}

namespace T1{
	cs int N=1e5+7;
	int el[N],nxt[N<<1|1],to[N<<1|1],ec;
	inline void adde(int u,int v){
		nxt[++ec]=el[u],el[u]=ec,to[ec]=v;
		nxt[++ec]=el[v],el[v]=ec,to[ec]=u;
	}
	int f[N],g[N],p,k;
	void dfs(int u,int p){
		f[u]=1,g[u]=k;
		for(int re e=el[u],v=to[e];e;v=to[e=nxt[e]])
		if(v!=p){dfs(v,u);
			int tp=add(f[v],g[v]);Mul(g[u],tp);
			Inc(g[u],mul(f[u],g[v]));Mul(f[u],tp);
		}
	}
	void main(){
		for(int re i=1;i<n;++i)adde(gi(),gi());
		p=inv(y)-1,k=mul(inv(p),n);dfs(1,0);
		cout<<mul(g[1],mul(po(mul(y,p),n),sqr(inv(n))))<<"\n";
	}
}

namespace T2{
	typedef std::vector<int> Poly;
	cs int bit=18,SIZE=1<<bit|7;
	int r[SIZE],*w[bit+1];
	int fac[SIZE],inv[SIZE],ifc[SIZE]; 
	inline void init_NTT(){
		for(int re i=1;i<=bit;++i)w[i]=new int[1<<i-1];
		int wn=po(3,mod-1>>bit);w[bit][0]=1;
		for(int re i=1;i<(1<<bit-1);++i)w[bit][i]=mul(w[bit][i-1],wn);
		for(int re i=bit-1;i;--i)
		for(int re j=0;j<(1<<i-1);++j)w[i][j]=w[i+1][j<<1];
		fac[0]=fac[1]=ifc[0]=ifc[1]=inv[0]=inv[1]=1;
		for(int re i=2;i<SIZE;++i){
			fac[i]=mul(fac[i-1],i);
			inv[i]=mul(mod-mod/i,inv[mod%i]);
			ifc[i]=mul(ifc[i-1],inv[i]);
		}
	}
	inline void NTT(int *A,int len,int typ){
		for(int re i=1;i<len;++i)if(i<r[i])std::swap(A[i],A[r[i]]);
		for(int re i=1,d=1;i<len;++d,i<<=1)
		for(int re j=0;j<len;j+=i<<1)
		for(int re k=0;k<i;++k){
			int &t1=A[j+k],&t2=A[i+j+k],t=mul(t2,w[d][k]);
			t2=dec(t1,t),Inc(t1,t);
		}
		if(typ==-1){
			std::reverse(A+1,A+len);
			for(int re i=0,iv=inv[len];i<len;++i)Mul(A[i],iv);
		}
	}
	inline void NTT(Poly &A,int len,int typ){NTT(&A[0],len,typ);}
	inline void init_rev(int l){
		for(int re i=1;i<l;++i)r[i]=r[i>>1]>>1|((i&1)?l>>1:0);
	}
	inline Poly operator*(Poly a,Poly b){
		int n=a.size(),m=b.size(),deg=n+m-1,l=1;
		while(l<deg)l<<=1;init_rev(l);
		a.resize(l),NTT(a,l,1);
		b.resize(l),NTT(b,l,1);
		for(int re i=0;i<l;++i)Mul(a[i],b[i]);
		NTT(a,l,-1);a.resize(deg);return a;
	}
	inline Poly Deriv(Poly a){
		for(int re i=0,la=a.size();i+1<la;++i)a[i]=mul(a[i+1],i+1);
		a.pop_back();return a;
	}
	inline Poly Integ(Poly a){a.push_back(0);
		for(int re i=a.size()-1;i;--i)a[i]=mul(a[i-1],inv[i]);
		a[0]=0;return a;
	}
	inline Poly Inv(cs Poly &a,int lim){
		int n=a.size();Poly c,b(1,::inv(a[0]));
		for(int re l=4;(l>>2)<lim;l<<=1){
			init_rev(l);c.resize(l>>1);
			for(int re i=0;i<(l>>1);++i)c[i]=i<n?a[i]:0;
			c.resize(l),NTT(c,l,1);
			b.resize(l),NTT(b,l,1);
			for(int re i=0;i<l;++i)
			Mul(b[i],dec(2,mul(b[i],c[i])));
			NTT(b,l,-1),b.resize(l>>1);
		}b.resize(lim);return b;
	}
	inline Poly Ln(Poly a,int lim){
		a=Integ(Deriv(a)*Inv(a,lim));
		return a.resize(lim),a;
	}
	inline Poly Exp(cs Poly &a){int lim=a.size();
		int n=a.size();Poly c,b(1,1);
		for(int re i=2;(i>>1)<lim;i<<=1){
			c=Ln(b,i);Dec(c[0],1);
			for(int re j=0;j<i;++j)c[j]=dec(j<n?a[j]:0,c[j]);
			b=b*c;b.resize(i);
		}b.resize(lim);return b;
	}
	void main(){
		init_NTT();int p=::inv(y)-1,k=mul(::inv(p),sqr(n));
		Poly f,g;f.resize(n+1);
		for(int re i=1;i<=n;++i)f[i]=mul(mul(k,ifc[i]),po(i,i));
		g=Exp(f);int ans=mul(g[n],fac[n]);
		cout<<mul(ans,mul(po(mul(y,p),n),po(::inv(n),4)))<<"\n";
	}
}

signed main(){
#ifdef zxyoi
	freopen("tree.in","r",stdin);
#endif
	n=gi(),y=gi(),op=gi();
	if(y==1)cout<<po(n,op*(n-2)),exit(0);
	switch(op){
		case 0:T0::main();break;
		case 1:T1::main();break;
		case 2:T2::main();break;
	}
	return 0;
} 

你可能感兴趣的:(多项式,生成函数)