zoukankan      html  css  js  c++  java
  • [WC2019]数树(树形dp+多项式exp)

    [WC2019]数树(树形dp+多项式exp)

    Part1

    相同边连接的点同一颜色,直接模拟即可

    namespace pt1{
    	int fa[N],sz[N];
    	map <int,int> M[N];
    	int Find(int x){ return fa[x]==x?x:fa[x]=Find(fa[x]); }
    	void Solve(){
    		rep(i,1,n) fa[i]=i;
    		rep(i,2,n){
    			int x=rd(),y=rd();
    			if(x>y) swap(x,y);
    			M[x][y]=1;
    		}
    		rep(i,2,n) {
    			int x=rd(),y=rd();
    			if(x>y) swap(x,y);
    			if(M[x][y]) fa[Find(x)]=Find(y);
    		}
    		int ans=1;
    		rep(i,1,n) if(Find(i)==i) ans=1ll*ans*y%P;
    		printf("%d
    ",ans);
    	}
    }
    
    

    Part2

    相同边连接的点同一颜色,即在相同边构成的树上形成了若干联通块

    很容易想到可以强制一些边保留,设保留(i)条边的方案数是(F_i),则答案就是(sum_i F_icdot y^{n-i})

    考虑(dp)那些边相同,但是不好直接计算剩下边不同的方案,所以考虑计算最多有(i)条边相同的方案数,即

    [G_i=sum_{j=i}C(j,i)F_j ]

    二项式反演得到(F_i=sum_{j=i}(-1)^{j-i}C(j,i)G_j)

    设分成了(m)个联通块,大小分别为(size_i),则这些联通块随意构成树的方案数就是(n^{m-2}cdotprod size_i)

    根据上述性质可以写出一个简单的(O(n^4))树形dp求得(G_i),即(dp[i][j][k])表示在(i)的子树里,有(j)条边相同,当前还剩下一个大小为(k)的联通块,每多转移一条相同边,系数是(frac{1}{ny})

    考虑优化(dp)

    联通块大小的问题,可以转化为每次在联通块里选择一个关键点的方案数,(dp)第三维(0/1)表示当前联通块里是否已经选出了关键点

    每次断开一个联通块时必须已经存在关键点

    答案是

    (sum_i F_icdot y^{n-i})

    (=sum_i y^{n-i} sum_{j=i}(-1)^{j-i}C(j,i)G_j)

    (=y^n G_jsum_{i=0}^j(-1)^{j-i}C(j,i)y^{-i})

    发现右边的式子(sum_0^j(-1)^{j-i}C(j,i)y^{-i}=(frac{1}{y}-1)^j)

    那么直接把(frac{1}{y}-1)带入作为保留一条边的转移系数,消去了第二维

    那么这个( ext{dp})可以被优化到(O(n))

    [ ]

    namespace pt2{
    	vector <int> G[N];
    	int dp[N][2],g[2],Inv;
    	void dfs(int u,int f){
    		dp[u][0]=dp[u][1]=1;
    		for(int v:G[u]) if(v!=f) {
    			dfs(v,u);
    			g[0]=g[1]=0;
    			rep(i,0,1) rep(j,0,1) {
    				if(!i||!j) g[i|j]=(g[i|j]+1ll*dp[u][i]*dp[v][j]%P*Inv)%P;
    				if(j) g[i]=(g[i]+1ll*dp[u][i]*dp[v][j])%P;
    			}
    			dp[u][0]=g[0],dp[u][1]=g[1];
    		}
    	}
    	void Solve() {
    		rep(i,2,n) {
    			int u=rd(),v=rd();
    			G[u].pb(v),G[v].pb(u);
    		}
    		Inv=(qpow(y)-1)*qpow(n)%P;
    		dfs(1,0);
    		ll res=dp[1][1]*qpow(y,n)%P*qpow(n,P+n-3)%P;
    		printf("%lld
    ",res);
    	}
    }
    

    Part3

    有了上面的(dp),这一部分就简单多了,设分成了(m)个联通块,每个大小为(a_i),则贡献为

    [egin{aligned}frac{n!cdot a_i^{a_i-2}cdot (n^{m-2})^2(frac{1}{y}-1)^{n-m}(frac{1}{n}^{n-m})^2cdot a_i^2}{prod a_i! m !}end{aligned} ]

    即枚举每个联通块生成树的数量,且需要考虑两棵树分别的联通块之间的连边数量,这一部分需要平方

    很显然,可以直接对于([x^i]F(x)=frac{1}{i!}cdot (frac{1}{n^2}cdot (frac{1}{y}-1))^{i-1} i^2i^{i-2})这个多项式求exp得到

    const int M=1<<18|10,K=17;
    	typedef vector <int> Poly;
    
    	int w[M],rev[M],Inv[M];
    	void Init(){
    		ll t=qpow(3,(P-1)>>K>>1);
    		w[1<<K]=1;
    		rep(i,(1<<K)+1,(1<<(K+1))-1) w[i]=w[i-1]*t%P;
    		drep(i,(1<<K)-1,1) w[i]=w[i<<1];
    		Inv[0]=Inv[1]=1;
    		rep(i,2,M-1) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
    	}
    	int Init(int n){
    		int R=1,cc=-1;
    		while(R<n) R<<=1,cc++;
    		rep(i,1,R-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<cc);
    		return R;
    	}
    	
    	void NTT(int n,Poly &a,int f){
    		if((int)a.size()<n) a.resize(n);
    		rep(i,1,n-1) if(rev[i]<i) swap(a[i],a[rev[i]]);
    		for(int i=1;i<n;i<<=1) {
    			int *e=w+i;
    			for(int l=0;l<n;l+=i*2){
    				for(int j=l;j<l+i;++j){
    					int t=1ll*a[j+i]*e[j-l]%P;
    					a[j+i]=a[j]-t,Mod2(a[j+i]);
    					a[j]+=t,Mod1(a[j]);
    				}
    			}
    		}
    		if(f==-1) {
    			reverse(a.begin()+1,a.end());
    			rep(i,0,n-1) a[i]=1ll*a[i]*Inv[n]%P;
    		}
    	}
    
    	Poly operator * (Poly a,Poly b){
    		int n=a.size(),m=b.size();
    		int R=Init(n+m-1);
    		NTT(R,a,1),NTT(R,b,1);
    		rep(i,0,R-1) a[i]=1ll*a[i]*b[i]%P;
    		NTT(R,a,-1),a.resize(n+m-1);
    		return a;
    	}
    
    	Poly Poly_Inv(Poly a){
    		int n=a.size();
    		if(n==1) return {(int)qpow(a[0])};
    		Poly b=a; b.resize((n+1)/2),b=Poly_Inv(b);
    		int R=Init(n*2);
    		NTT(R,a,1),NTT(R,b,1);
    		rep(i,0,R-1) a[i]=1ll*b[i]*(2-1ll*a[i]*b[i]%P+P)%P;
    		NTT(R,a,-1); a.resize(n);
    		return a;
    	}
    
    	Poly Deri(Poly a){
    		rep(i,1,a.size()-1) a[i-1]=1ll*i*a[i]%P;
    		a.pop_back();
    		return a;
    	}
    	Poly IDeri(Poly a){
    		a.pb(0);
    		drep(i,a.size()-2,0) a[i+1]=1ll*a[i]*Inv[i+1]%P;
    		a[0]=0;
    		return a;
    	}
    
    	Poly Ln(Poly a){
    		int n=a.size();
    		a=Deri(a)*Poly_Inv(a),a.resize(n+1);
    		return IDeri(a);
    	}
    
    	Poly Exp(Poly a){
    		int n=a.size();
    		if(n==1) return Poly{1};
    		Poly b=a; b.resize((n+1)/2),b=Exp(b);
    		b.resize(n); Poly c=Ln(b);
    		rep(i,0,n-1) c[i]=a[i]-c[i],Mod2(c[i]);
    		c[0]++,c=c*b;
    		c.resize(n);
    		return c;
    	}
    	
    	void Solve() {
    		int I=(qpow(y)-1)*qpow(1ll*n*n%P)%P;
    		Init();
    		Poly F(n+1);
    		for(int i=1,FInv=1;i<=n;FInv=1ll*FInv*Inv[++i]%P){
    			F[i]=qpow(I,(i-1)) * // 保留i-1条边
    			(i==1?1:qpow(i,i-2))%P   // i个点生成树
    			*  i%P * i%P // 
    			* FInv%P; // 阶乘常数
    		}
    		F=Exp(F);
    		rep(i,1,n) F[n]=1ll*F[n]*i%P;
    		ll res=F[n]*qpow(y,n)%P*qpow(n,2*(P+n-3))%P;
    		printf("%lld
    ",res);
    	}
    
  • 相关阅读:
    io学习
    asp.net文件上传进度条研究
    asp.net页面中的Console.WriteLine结果如何查看
    谨慎跟随初始目的不被关联问题带偏
    android 按钮特效 波纹 Android button effects ripple
    安卓工作室 日志设置
    安卓工作室 文件浏览器 android studio File browser
    一个新的Android Studio 2.3.3可以在稳定的频道中使用。A new Android Studio 2.3.3 is available in the stable channel.
    新巴巴运动网上商城 项目 快速搭建 教程 The new babar sports online mall project quickly builds a tutorial
    码云,git使用 教程-便签
  • 原文地址:https://www.cnblogs.com/chasedeath/p/13143207.html
Copyright © 2011-2022 走看看