【题解】「WC2019」数树

NOI十合一

请先保证有足够的耐心看完本题解

首先,若两树的公共边有\(k\)条,则给予数方案总共有\(y^{n-k}\)种。我有一个很好的证明,可惜这里太小写不下。

这样就能轻松通过subtask1了。

显然,subtask1在引导我们向公共边的数量取思考。

\(f(i)\)表示两棵树\(i\)条公共边的的方案数(注意,不是给予数方案数)。则

\[\large \begin{align} ans&=\sum_{i=0}^{n-1}y^{n-i}f(i)\\ &=y^n\sum_{i=0}^{n-1}y^{-i}f(i)\\ \end{align} \]

然而,直接求\(f(i)\)并不好求,于是可以设\(g(i)\)表示两棵树有\(i\)确定的公共边的的方案数。可以得到,

\[\large g(i)=\sum_{j=i}^{n-1}C_j^if(j) \]

可以由每一个恰有\(j\)条边的方案中选择\(i\)条边推得。

观察到\(f(j)\)乘的组合数都是\(C_j^*\)类型的,于是就有一个神奇的变换:

\[\large \begin{align} ans&=y^n\sum_{i=0}^{n-1}(\frac 1y)^if(i)\\ &=y^n\sum_{i=0}^{n-1}\sum_{j=0}^if(i)C_i^j(\frac 1y-1)^j\\ &=y^n\sum_{j=0}^{n-1}(\frac 1y-1)^j\sum_{i=j}^{n-1}C_i^jf(i)\\ &=y^n\sum_{i=0}^{n-1}(\frac 1y-1)^ig(i) \end{align} \]

那么,\(g(i)\)该怎么求呢?

对于subtask2,设选出来的\(i\)条公共边构成的\(n-i\)个连通块大小分别为\(a_1,a_2,\cdots,a_{n-i}\),则有:

\[\large g(i)=\sum_an^{n-i-2}\prod_{j=1}^{n-i}a_j \]

证明:将每个连通块缩点,再进行生成树。设树边由儿子连向父亲,则prufer序的每一位都可以选择任意一块,每一块都可以选择任意一个点,这样入边总共产生了\(n^{n-i-2}\)的贡献。再乘上出边的\(\prod a_j\)的贡献。

所以,设\(p=\frac 1y-1\),有:

\[\large \begin{align} ans&=y^n\sum_{i=0}^{n-1}p^in^{n-i-2}\sum_a\prod_{j=1}^{n-i}a_j\\ &=y^np^nn^{-2}\sum_a\prod_{j=1}^{|a|}a_j\times \frac np \end{align} \]

\(v=\frac np\),根据上面的方程,很容易列出一个简单的\(dp\),设\(dp_{i,j}\)表示,当前讨论了以\(i\)号点为根的子树,该子树内还有\(j\)个点没被划进公共边连通块内。显然转移是一个卷积式,可以设\(h_i(x)\)\(i\)号点的生成函数。列出方程:

\[\large \begin{align} &h_i(x)=\sum_{j=0}^\infty dp_{i,j}x^j\\ &h_i(x)=dp_{i,0}+x\prod_{j\in son_i}h_j(x)\\ &dp_{i,0}=v\sum_{j=1}^\infty dp_{i,j}\times j=v\times h_i'(1)\\ \therefore&\ h_i(x)=v\times h_i'(1)+x\prod_{j\in son_i}h_j(x) \end{align} \]

容易发现,\(ans=y^np^nn^{-2}dp_{1,0}=y^np^nn^{-2}v\times h_1'(1)\),所以,计算时只用到了\(h_i(1)\)\(h_i'(1)\)。直接套用式子

\[\large \begin{align} h_i'(1)&=(1+\sum_{j\in son_i}\frac{h_j'(1)}{h_j(1)})\prod_{j\in son_i}h_j(1)\\ h_i&=h_i'(1)+\prod_{j\in son_i}h_j(1) \end{align} \]

\(O(n\log998244353)\)搞定。

接着看subtask3,求\(g(i)\)的方法类似上一个子任务。不过结果有点不同:

\[\large g(i)=\sum_{a}n^{2(n-i-2)}\prod_{j=1}^{n-i}a_j^{a_j}=n^{-4}\sum_a\prod_{j=1}^{n-i}a_j^{a_j}n^2 \]

很简单,就是将之前的\(g(i)\)平方,然后对每一个连通块再乘一个生成树数量\(a_j^{a_j-2}\)

但是在数连通块的时候很容易数重,可以把所有连通块先按照大小排序,再按照最小节点编号排序。设大小为\(a\)的连通块有\(t_a\)个,则

\[\large \begin{align} g(i)&=n^{-4}n!\sum_t\prod_{a=1}^n\frac{(\frac{a^an^2}{a!})^{t_a}}{t_a!}\\ ans&=y^n\sum_{i=0}^{n=1}p^ig(i)\\ &=y^np^nn^{-4}n!\sum_t\prod_{a=1}^n\frac{(\frac{a^an^2}{a!p})^{t_a}}{t_a!} \end{align} \]

\(v_a=\frac{a^an^2}{a!p}\),则

\[\large ans=y^np^nn^{-4}n!\sum_t\prod_{a=1}^n\frac{v_a^{t_a}}{t_a!} \]

又由于\(\sum t_aa=n\),设

\[\large h_a(x)=\sum_{t=0}^\infty \frac{v_a^tx^{at}}{t!}=e^{v_ax^a} \]

\[\large \begin{align} ans&=y^np^nn^{-4}n!(\prod_{a=1}^{n}h_a(x))[x^n]\\ &=y^np^nn^{-4}n!(\exp(\sum v_ax^a))[x^n] \end{align} \]

先处理出\(v\),再用多项式\(\exp\)就可以解决。复杂度\(O(n\log n)\)

code(去掉了namespace poly):

#include<stdio.h>
#include<vector>
#include<algorithm>
#define inf 998244353
int n,y,hdhdAKIOI;
namespace sub0{
	std::pair<int,int>a[200002];
	void solve(){
		for(int i=1;i<=n+n-2;i++){
			int p,q;scanf("%d%d",&p,&q);
			if(p>q)p^=q^=p^=q;a[i]=std::make_pair(p,q);
		}std::sort(a+1,a+n+n-1);
		int cnt=n;
		for(int i=2;i<=n+n-1;i++)cnt-=a[i]==a[i-1];
		printf("%d",poly::ksm(y,cnt));
	}
}
namespace sub1{
	int f[100002][2],v;
	int Last[100002],Next[200002],End[200002];
	void dfs(int p,int F){
		register unsigned long long s=1,cnt=1;
		for(int i=Last[p];i;i=Next[i])if(End[i]!=F){
			dfs(End[i],p);
			s=s*f[End[i]][0]%inf;
			cnt+=1ull*f[End[i]][1]*poly::getinv(f[End[i]][0]);
			if(cnt>=1ull*inf*inf)cnt-=1ull*inf*inf;
		}cnt%=inf;
		f[p][1]=s*cnt%inf;
		f[p][0]=(s+1ull*v*f[p][1])%inf;
	}
	void solve(){
		if(y==1)return void(printf("%d",poly::ksm(n,n-2)));
		for(int i=1;i<n+n-2;i+=2){
			scanf("%d%d",&End[i+1],&End[i]);
			Next[i]=Last[End[i+1]];Last[End[i+1]]=i;
			Next[i+1]=Last[End[i]];Last[End[i]]=i+1;
		}int p=poly::getinv(y)-1;
		v=1ull*poly::getinv(p)*n%inf;
		dfs(1,0);
		printf("%lld\n",1ull*f[1][1]*v%inf*poly::ksm(1ull*y*p%inf,n)%inf*poly::ksm(n,inf-3)%inf);
	}
}
namespace sub2{
	int v[524288],tmp1[524288],tmp2[524288],a[524288],fac[524288],ifac[524288];
	void solve(){
		if(y==1)return void(printf("%d",poly::ksm(n,n*2-4)));
		fac[0]=1;
		for(int i=1;i<=n;i++)
			fac[i]=1ull*i*fac[i-1]%inf;
		ifac[n]=poly::getinv(fac[n]);
		for(int i=n-1;i>=0;i--)
			ifac[i]=1ull*(i+1)*ifac[i+1]%inf;
		int p=poly::getinv(y)-1,invp=poly::getinv(p);
		for(int i=1;i<=n;i++)
			v[i]=1ull*poly::ksm(i,i+inf-1)*ifac[i]%inf*n%inf*n%inf*invp%inf;
		int len=poly::gett(n);
		poly::getexp(v,a,tmp1,tmp2,len);
		printf("%lld",1ull*a[n]*poly::ksm(1ull*y*p%inf,n)%inf*poly::ksm(n,inf-5)%inf*fac[n]%inf);
	}
}
int main(){
	poly::init();
	scanf("%d%d%d",&n,&y,&hdhdAKIOI);
	if(hdhdAKIOI==0)sub0::solve();
	else if(hdhdAKIOI==1)sub1::solve();
	else if(hdhdAKIOI==2)sub2::solve();
}
posted @ 2020-02-25 23:03  ztc…  阅读(273)  评论(0编辑  收藏  举报

Please contact lydsy2012@163.com!