zoukankan      html  css  js  c++  java
  • 【题解】「WC2019」数树

    NOI十合一

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

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

    这样就能轻松通过subtask1了。

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

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

    [large egin{align} ans&=sum_{i=0}^{n-1}y^{n-i}f(i)\ &=y^nsum_{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 egin{align} ans&=y^nsum_{i=0}^{n-1}(frac 1y)^if(i)\ &=y^nsum_{i=0}^{n-1}sum_{j=0}^if(i)C_i^j(frac 1y-1)^j\ &=y^nsum_{j=0}^{n-1}(frac 1y-1)^jsum_{i=j}^{n-1}C_i^jf(i)\ &=y^nsum_{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 egin{align} ans&=y^nsum_{i=0}^{n-1}p^in^{n-i-2}sum_aprod_{j=1}^{n-i}a_j\ &=y^np^nn^{-2}sum_aprod_{j=1}^{|a|}a_j imes frac np end{align} ]

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

    [large egin{align} &h_i(x)=sum_{j=0}^infty dp_{i,j}x^j\ &h_i(x)=dp_{i,0}+xprod_{jin son_i}h_j(x)\ &dp_{i,0}=vsum_{j=1}^infty dp_{i,j} imes j=v imes h_i'(1)\ herefore& h_i(x)=v imes h_i'(1)+xprod_{jin son_i}h_j(x) end{align} ]

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

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

    (O(nlog998244353))搞定。

    接着看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_aprod_{j=1}^{n-i}a_j^{a_j}n^2 ]

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

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

    [large egin{align} g(i)&=n^{-4}n!sum_tprod_{a=1}^nfrac{(frac{a^an^2}{a!})^{t_a}}{t_a!}\ ans&=y^nsum_{i=0}^{n=1}p^ig(i)\ &=y^np^nn^{-4}n!sum_tprod_{a=1}^nfrac{(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_tprod_{a=1}^nfrac{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 egin{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(nlog 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
    ",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();
    }
    
  • 相关阅读:
    「算法笔记」斜率优化
    「算法笔记」多项式求逆
    「算法笔记」霍尔定理
    「算法笔记」Min_25 筛
    「算法笔记」点分治
    「算法笔记」生成函数入门
    「算法笔记」快速数论变换(NTT)
    Spring Boot+Vue全栈开发实战PDF+源代码
    宅米网性能优化实践
    PHP 性能分析第一篇: Xhprof & Xhgui 介绍
  • 原文地址:https://www.cnblogs.com/ztc03/p/12364494.html
Copyright © 2011-2022 走看看