洛谷P5206/loj2983 [WC2019]数树

今年年初,WC2019上,我继续扮演爆零士,Q1Q2双爆零,弘扬爆零文化,希望大家多多资磁。

y=1的subtask输出1的就是在下

这位大佬写的题解真是精妙啊->here

Question0

m m m为颜色种类, T 1 T_1 T1为树1的边集, T 2 T_2 T2为树2的边集,那么答案就是 m n − ∣ T 1 ∩ T 2 ∣ m^{n-|T_1 \cap T_2|} mnT1T2,用个map哈希一下即可。

Question1

基本推导

T 1 T_1 T1已经给定了, T 2 T_2 T2未知。

a n s = m n − ∣ T 1 ∩ T 2 ∣ = m n m − ∣ T 1 ∩ T 2 ∣ ans=m^{n-|T_1 \cap T_2|}=m^nm^{-|T_1 \cap T_2|} ans=mnT1T2=mnmT1T2,将前面的定值 m n m^n mn暂时放在一边。

F ( S ) F(S) F(S) T 1 ∩ T 2 = S T_1 \cap T_2=S T1T2=S T 2 T_2 T2的个数

G ( S ) G(S) G(S)为满足存在边集 S S S T 2 T_2 T2个数

根据集合反演的那一套理论,因为 G ( S ) = ∑ S ⊂ T F ( T ) G(S)=\sum_{S \subset T} F(T) G(S)=STF(T),所以 F ( S ) = ∑ S ⊂ T G ( T ) ( − 1 ) ∣ T − S ∣ = ∑ S ⊂ T G ( T ) ( − 1 ) ∣ T ∣ ( − 1 ) − ∣ S ∣ F(S)=\sum_{S \subset T} G(T) (-1)^{|T-S|}=\sum_{S \subset T} G(T) (-1)^{|T|}(-1)^{-|S|} F(S)=STG(T)(1)TS=STG(T)(1)T(1)S

a n s = ∑ S F ( S ) m − ∣ S ∣ = ∑ T G ( T ) ( − 1 ) ∣ T ∣ ∑ S ⊂ T ( − m ) − ∣ S ∣ ans=\sum_S F(S)m^{-|S|}=\sum_T G(T)(-1)^{|T|}\sum_{S \subset T} (-m)^{-|S|} ans=SF(S)mS=TG(T)(1)TST(m)S

a n s = ∑ T G ( T ) ( − 1 ) ∣ T ∣ ∑ i = 0 ∣ T ∣ ( − m ) − i C ∣ T ∣ i ans=\sum_{T} G(T)(-1)^{|T|} \sum_{i=0}^{|T|} (-m)^{-i} C_{|T|}^i ans=TG(T)(1)Ti=0T(m)iCTi

a n s = ∑ T G ( T ) ( − 1 ) ∣ T ∣ ∑ i = 0 ∣ T ∣ C ∣ T ∣ i ( − 1 m ) i 1 ∣ T ∣ − i ans=\sum_{T} G(T)(-1)^{|T|} \sum_{i=0}^{|T|} C_{|T|}^i (-\frac{1}{m})^i 1^{|T|-i} ans=TG(T)(1)Ti=0TCTi(m1)i1Ti

a n s = ∑ T G ( T ) ( 1 m − 1 ) ∣ T ∣ ans=\sum_{T} G(T) (\frac{1}{m}-1)^{|T|} ans=TG(T)(m11)T

G(T)咋求

那么现在问题来了: G ( T ) G(T) G(T)咋求?

假设边集 T T T将原图划分为了 k k k个连通块( k = n − ∣ T ∣ k=n-|T| k=nT),其中第 i i i个连通块的大小为 a i a_i ai。那么存在集合 S S S中的边的树的个数,等价于将每个连通块看成一个点,点 i i i和点 j j j之间有 a i a j a_ia_j aiaj条不同的边的生成树方案数。

端上矩阵树定理,手玩行列式(过程略,反正就是一行同时除以一个数啊,一列同时减其他列啊之类的操作把行列式消成上三角即可)得 n k − 2 ∏ i = 1 k a i n^{k-2}\prod_{i=1}^k a_i nk2i=1kai

那么 a n s = ∑ T n n − ∣ T ∣ − 2 ( 1 m − 1 ) ∣ T ∣ ∏ a i ans=\sum_{T} n^{n-|T|-2} (\frac{1}{m}-1)^{|T|}\prod a_i ans=TnnT2(m11)Tai

a n s = ( 1 m − 1 ) n n 2 ∑ T ∏ a i n 1 m − 1 ans=\frac{(\frac{1}{m}-1)^n}{n^2}\sum_T \prod a_i\frac{n}{\frac{1}{m}-1} ans=n2(m11)nTaim11n

前面定值 ( 1 m − 1 ) n n 2 \frac{(\frac{1}{m}-1)^n}{n^2} n2(m11)n暂时放在一边。(“一边”里有的东西: ( 1 m − 1 ) n n 2 m n \frac{(\frac{1}{m}-1)^n}{n^2}m^n n2(m11)nmn

DP?生成函数!

一大堆参数看着烦,记 n 1 m − 1 = w \frac{n}{\frac{1}{m}-1}=w m11n=w

答案是所有边集的价值和,一个边集的价值等于所有连通块的价值的乘积,一个连通块的价值等于连通块大小乘以 w w w。因为边集中的边都应该是 T 1 T_1 T1里有的边,所以可以一边在 T 1 T_1 T1上dfs一边DP。

f ( x , i ) f(x,i) f(x,i)表示,所有能够满足在以 x x x为根的子树中, x x x所在的连通块大小为 i i i的边集,它们贡献的不包括 x x x所在的连通块的所有连通块价值乘积的和。

只要考虑 x x x与每个儿子之间的边是否存在即可DP,复杂度 O ( n 2 ) O(n^2) O(n2)

构造生成函数 f x ( x ) f_x(x) fx(x),其中 i i i次项的系数为 f ( x , i ) f(x,i) f(x,i)

如果要算包括 x x x所在连通块的所有连通块价值乘积的话,就是 w f x ′ ( 1 ) wf_x'(1) wfx(1),记这个东西为 g x g_x gx

记DP过程中,考虑到儿子 y y y之前算出的 f x f_x fx f t f_t ft,那么:

f x ( x ) = f t ( x ) ( f y ( x ) + g y ) f_x(x)=f_t(x)(f_y(x)+g_y) fx(x)=ft(x)(fy(x)+gy)

于是有 f x ′ ( x ) = ( f t ( x ) f y ( x ) + f t ( x ) g y ) ′ = g y f t ′ ( x ) + f t ′ ( x ) f y ( x ) + f y ′ ( x ) f t ( x ) f_x'(x)=(f_t(x)f_y(x)+f_t(x)g_y)'=g_yf_t'(x)+f_t'(x)f_y(x)+f_y'(x)f_t(x) fx(x)=(ft(x)fy(x)+ft(x)gy)=gyft(x)+ft(x)fy(x)+fy(x)ft(x)

所以 g x = g y g t + g t f y ( 1 ) + g y f t ( 1 ) g_x=g_yg_t+g_tf_y(1)+g_yf_t(1) gx=gygt+gtfy(1)+gyft(1)

f ( 1 ) f(1) f(1) h h h,则得到两个DP式:

g x = g y g t + g t h y + g y h t g_x=g_yg_t+g_th_y+g_yh_t gx=gygt+gthy+gyht h x = h t ( h y + g y ) h_x=h_t(h_y+g_y) hx=ht(hy+gy)

于是就可以 O ( n ) O(n) O(n)地DP出 g 1 g_1 g1,然后答案就是 ( 1 m − 1 ) n n 2 m n g 1 \frac{(\frac{1}{m}-1)^n}{n^2}m^n g_1 n2(m11)nmng1

Question2

F ( S ) F(S) F(S)的意义更改为满足 T 1 ∩ T 2 = S T_1 \cap T_2=S T1T2=S的树对 ( T 1 , T 2 ) (T_1,T_2) (T1,T2)的个数

那么对应的就是 G ( S ) 2 G(S)^2 G(S)2

子集反演啊,变换啊, G ( S ) G(S) G(S)怎么算啊都一样地搞(即“基本推导”和“G(T)咋求”两段),得到:

a n s = ∑ S ∏ a i 2 n 2 1 m − 1 ans=\sum_S \prod \frac{a_i^2n^2}{\frac{1}{m}-1} ans=Sm11ai2n2,放在一边的是 m n ( 1 m − 1 ) n n 4 m^n\frac{(\frac{1}{m}-1)^n}{n^4} mnn4(m11)n

w = n 2 1 m − 1 w=\frac{n^2}{\frac{1}{m}-1} w=m11n2

接下来一个边集的价值就是所有连通块的价值乘积,连通块价值是连通块大小的平方乘 w w w。因为 T 1 T_1 T1 T 2 T_2 T2都未知,所以所有边集的价值和就是所有 n n n个节点的森林的价值和。

n n n个节点的树有 n n − 2 n^{n-2} nn2个(prufer编码经典结论),所以 n n n个节点的所有树的贡献是 w n n wn^n wnn

构造指数型生成函数, f ( x ) f(x) f(x) i i i次项是 i i i个节点的所有树的贡献( f i = w i i i ! ) f_i=\frac{wi^i}{i!}) fi=i!wii g ( x ) g(x) g(x) i i i次项是 i i i个节点所有森林的贡献。

图计数问题中,构造指数型生成函数,从连通到不连通是求exp,从不连通到连通是求ln。

所以 g ( x ) = e x p ( l n ( x ) ) g(x)=exp(ln(x)) g(x)=exp(ln(x)),答案是 g n ( n ! ) ( m n ) ( 1 m − 1 ) n n 4 g_n (n!) (m^n)\frac{(\frac{1}{m}-1)^n}{n^4} gn(n!)(mn)n4(m11)n

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
} 
const int mod=998244353,N=100005,LIM=262150;
int n,m,op;
typedef pair<int,int> PR;

int qm(int x) {return x>=mod?x-mod:x;}
int ksm(int x,int y) {
	int re=1;
	for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
	return re;
}

namespace work0{
	map<PR,int> mp;
	void work() {
		int x,y,ans=n;
		for(RI i=1;i<n;++i) {
			x=read(),y=read(); if(x>y) swap(x,y);
			mp[(PR){x,y}]=1;
		}
		for(RI i=1;i<n;++i) {
			x=read(),y=read(); if(x>y) swap(x,y);
			if(mp[(PR){x,y}]) --ans;
		}
		printf("%d\n",ksm(m,ans));
	}
}

namespace work1{
	int tot,w,ans,h[N],ne[N<<1],to[N<<1],G[N],H[N];
	void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
	void dfs(int x,int las) {
		G[x]=w,H[x]=1;
		for(RI i=h[x];i;i=ne[i]) {
			if(to[i]==las) continue;
			dfs(to[i],x);
			int tG=G[x],tH=H[x];
			H[x]=1LL*tH*qm(G[to[i]]+H[to[i]])%mod;
			G[x]=qm(qm(1LL*tG*G[to[i]]%mod+1LL*tG*H[to[i]]%mod)+1LL*G[to[i]]*tH%mod);
		}
	}
	void work() {
		if(m==1) {printf("%d\n",ksm(n,n-2));return;}
		int x,y,kl=qm(ksm(m,mod-2)-1+mod);
		for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
		w=1LL*n*ksm(kl,mod-2)%mod,dfs(1,0);
		ans=1LL*G[1]*ksm(m,n)%mod*ksm(kl,n)%mod*ksm(ksm(n,2),mod-2)%mod;
		printf("%d\n",ans);
	}
}

namespace work2{
	int w,ans;
	int F[LIM],G[LIM],rev[LIM],len[LIM],inv[LIM];
	int k1[LIM],k2[LIM],k3[LIM],k4[LIM],k5[LIM];
	void NTT(int *a,int n,int x) {
		for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
		for(RI i=1;i<n;i<<=1) {
			int gn=ksm(3,(mod-1)/(i<<1));
			for(RI j=0;j<n;j+=(i<<1)) {
				int t1,t2,g=1;
				for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
					t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
					a[j+k]=qm(t1+t2),a[j+i+k]=qm(t1-t2+mod);
				}
			}
		}
		if(x==1) return;
		reverse(a+1,a+n);for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv[n]%mod;
	}
	void getrev(int n)
		{for(RI i=0;i<n;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len[n]-1));}
	void getJF(int *a,int *b,int n)
		{for(RI i=1;i<n;++i) b[i]=1LL*inv[i]*a[i-1]%mod; b[0]=0;}
	void getDao(int *a,int *b,int n)
		{for(RI i=1;i<n;++i) b[i-1]=1LL*a[i]*i%mod; b[n-1]=0;}
	void getinv(int *a,int *b,int n) {
		if(n==1) {b[0]=ksm(a[0],mod-2),b[1]=0;return;}
		getinv(a,b,n>>1);int kn=n<<1;
		for(RI i=0;i<n;++i) k5[i]=a[i],k5[i+n]=b[i+n]=0;
		getrev(kn),NTT(k5,kn,1),NTT(b,kn,1);
		for(RI i=0;i<kn;++i) b[i]=1LL*b[i]*qm(2-1LL*k5[i]*b[i]%mod+mod)%mod;
		NTT(b,kn,-1);
		for(RI i=n;i<kn;++i) b[i]=0;
	}
	void getln(int *a,int *b,int n) {
		getDao(a,k3,n),getinv(a,k4,n);
		int kn=n<<1;for(RI i=n;i<kn;++i) k3[i]=k4[i]=0;
		getrev(kn),NTT(k3,kn,1),NTT(k4,kn,1);
		for(RI i=0;i<kn;++i) k3[i]=1LL*k3[i]*k4[i]%mod;
		NTT(k3,kn,-1),getJF(k3,b,n);
		for(RI i=n;i<kn;++i) b[i]=0;
	}
	void getexp(int *a,int *b,int n) {
		if(n==1) {b[0]=1,b[1]=0;return;}
		getexp(a,b,n>>1);int kn=n<<1;
		getln(b,k1,n);
		for(RI i=0;i<n;++i) k2[i]=qm(a[i]-k1[i]+mod),k2[i+n]=b[i+n]=0;
		k2[0]=qm(k2[0]+1);
		getrev(kn),NTT(b,kn,1),NTT(k2,kn,1);
		for(RI i=0;i<kn;++i) b[i]=1LL*b[i]*k2[i]%mod;
		NTT(b,kn,-1);
		for(RI i=n;i<kn;++i) b[i]=0;
	}
	void work() {
		if(m==1) {printf("%d\n",ksm(n,2*n-4));return;}
		int kl=qm(ksm(m,mod-2)-1+mod);
		w=1LL*n*n%mod*ksm(kl,mod-2)%mod;
		int kn=1;while(kn<=n) kn<<=1,len[kn]=len[kn>>1]+1;
		len[kn<<1]=len[kn]+1;
		inv[0]=inv[1]=1;
		for(RI i=2;i<=(kn<<1);++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
		int invfac=1,fac=1;
		for(RI i=1;i<=n;++i)
			invfac=1LL*invfac*inv[i]%mod,F[i]=1LL*ksm(i,i)*w%mod*invfac%mod;
		getexp(F,G,kn);
		for(RI i=1;i<=n;++i) fac=1LL*fac*i%mod;
		ans=1LL*G[n]*ksm(m,n)%mod*ksm(kl,n)%mod*fac%mod*ksm(ksm(n,4),mod-2)%mod;
		printf("%d\n",ans);
	}
}

int main()
{
	n=read(),m=read(),op=read();
	if(op==0) work0::work();
	else if(op==1) work1::work();
	else work2::work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值