CF917D Stranger Trees

CXX.CF917D Stranger Trees

这里是本题的DP解法。矩阵树定理解法详见矩阵树定理学习笔记中重题III.TopCoder13369-TreeDistance

首先,一个基础结论是,如果一张 \(n\) 个点的图,被连成一棵森林,则继续加边连成一棵树的方案数是 \(n^{k-2}\prod\limits_{i=1}^kc_i\),其中森林中共有 \(k\) 棵树,第 \(i\) 棵树的大小是 \(c_i\)

然后,考虑二项式反演。设 \(g_i\) 是至多有 \(i\) 条边在原树上的方案数。二项式反演一下即可得到恰好有 \(i\) 条边在原树上的方案数。则问题被转化为求出 \(g\) 数组。\(g\) 数组中 \(n^{k-2}\) 的部分是可以提出来统一计算的。考虑使用DP来求出 \(g\) 的另一部分。设 \(f_{i,j,k}\) 表示 \(i\) 的子树中被截成 \(j\) 棵树,且 \(i\) 节点自身所在树大小为 \(k\) 的权值(不包括 \(i\) 自身的树)和。合并节点 \(i\) 和儿子 \(i'\) 时,只需对二者处于同一连通块或是不同连通块进行树上背包分开转移即可。

时间复杂度 \(O(n^4)\),是树上背包复杂度。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,f[110][110][110],g[110],sz[110],h[110][110],fac[110],inv[110];
vector<int>v[110];
int ksm(int x,int y=mod-2){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;
	return z;
}
void dfs(int x,int fa){
	sz[x]=1,f[x][1][1]=1;
	for(auto y:v[x]){
		if(y==fa)continue;
		dfs(y,x);
		for(int i=1;i<=sz[x];i++)for(int I=1;I<=sz[y];I++)for(int j=1;j<=sz[x];j++)for(int J=1;J<=sz[y];J++){
			(h[i+I][j]+=1ll*f[x][i][j]*f[y][I][J]%mod*J%mod)%=mod;
			(h[i+I-1][j+J]+=1ll*f[x][i][j]*f[y][I][J]%mod)%=mod;
		}
		for(int i=1;i<=sz[x]+sz[y];i++)for(int j=1;j<=sz[x]+sz[y];j++)f[x][i][j]=h[i][j],h[i][j]=0;
		sz[x]+=sz[y];
	}
}
int C(int x,int y){return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}
int main(){
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	fac[0]=1;for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv[n]=ksm(fac[n]);for(int i=n-1;i>=0;i--)inv[i]=1ll*inv[i+1]*(i+1)%mod;
	dfs(1,0);
	for(int i=0,k=ksm(n);i<n;i++,k=1ll*k*n%mod){
		for(int j=1;j<=n;j++)(g[i]+=1ll*f[1][i+1][j]*j%mod)%=mod;
		g[i]=1ll*g[i]*k%mod;
	}
	for(int i=0;i<n;i++)for(int j=0;j<i;j++)(g[i]+=mod-1ll*g[j]*C(n-j-1,i-j)%mod)%=mod;
	for(int i=0;i<n;i++)printf("%d ",g[n-i-1]);
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14601370.html