QOJ 5357 芒果冰加了空气

题目传送门

首先计数题基本上要么数学要么 dp。这题怎么看都不像能数学的样子,于是考虑 dp。

首先对原树 dp 发现根本找不到任何有用的状态,考虑还有什么东西是有用的。

考虑能不能对点分树进行 dp。比如现在原树上有一条边 \((u,v)\),我们就要把这两个点当前的点分树合并。不妨设 \(u\) 所在点分树根为 \(x\)\(v\) 所在点分树根为 \(y\)

这里注意到一个性质:我们只需要合并 \(x\rightarrow u\)\(y\rightarrow v\) 两条链,两点所在点分树的其他部分我们不关心。这是因为,当我们把这两条链里的点的顺序确定了之后,合并后的点分树就唯一确定了。

这样的方案数相当于按顺序合并两个长度分别为 \(n,m\) 的序列,即 \(\binom{n+m}{n}\)

然后我们直接 dp,设 \(f_{u,x}\) 表示 \(u\) 当前的点分树,\(u\) 在该树里的深度为 \(x\) 的方案数。不难发现 \(f_{u,x},f_{v,y}\) 可以转移到 \(f_{u,x\sim x+y}\)。转移方程是显然的。

但是这样直接做是 \(O(n^3)\) 的。不难发现,我们只需枚举 \(x,z\),那么 \(y\) 的范围就确定了,为 \(z-x\sim siz_v\)。于是我们对 \(f_v\) 做一个后缀和优化转移即可,时间复杂度 \(O(n^2)\)

AC code:

#include<bits/stdc++.h>
#define int long long
#define N 5005
#define mod 1000000007
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
int T=1,n,f[N][N],s[N],tmp[N],c[N][N],s2[N];
vector<int>e[N];
void add(int a,int b){
	e[a].push_back(b);
}
void dfs(int u,int fa){
	f[u][1]=1;
	s[u]=1;
	for(auto j:e[u]){
		if(j==fa)continue;
		dfs(j,u);
		for(int x=1;x<=s[u];x++){
			tmp[x]=f[u][x];
			f[u][x]=0;
		}
		s2[s[j]+1]=0;
		for(int y=s[j];~y;y--){
			s2[y]=(s2[y+1]+f[j][y])%mod;
		}
		for(int x=1;x<=s[u];x++){
			for(int z=x;z<=x+s[j];z++){
				(f[u][z]+=tmp[x]*c[z-1][z-x]%mod*s2[z-x]%mod)%=mod;
			}
		}
		s[u]+=s[j];
	}
}
void solve(int cs){
	cin>>n;
	for(int i=0;i<=n;i++){
		c[i][0]=1;
		for(int j=1;j<=i;j++){
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
	}
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	dfs(1,0);
	int res=0;
	for(int i=1;i<=n;i++){
		(res+=f[1][i])%=mod;
	}
	cout<<res<<'\n';
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//	cin>>T;
//	init();
	for(int cs=1;cs<=T;cs++){
		solve(cs);
	}
	return 0;
}
posted @ 2025-03-27 11:59  zxh923  阅读(161)  评论(0)    收藏  举报