Examples

AGC058F Authentic Tree DP 解题报告

AGC058F Authentic Tree DP 解题报告:

题意

令一棵树的函数 \(f(T)\) 为:

\(T\) 为单点,\(f(T)=1\)

否则,对于边 \(e\),设 \(A_e,B_e\) 为割掉 \(e\) 后产生的两个连通块:

\[f(T)=\frac{\sum_{e\in T}f(A_e)f(B_e)}{n} \]

给定一棵树,计算其权值。

\(1\leqslant n\leqslant 5000\)

分析

最近好多人都在改,就把这篇博客发出来了。

很厉害的一道题目啊!!!

这道题巧妙利用了取模下的性质:\(x\) 的逆元等于 \(x+P\) 的逆元。(\(P=998244353\)

看到这个转移方程,肯定要赋予其一个组合意义:

将每条边建出一个对应点(称其为“新点”)连接其原来连接的两个点,然后在新点下面加入 \(P-1\) 个叶子。

然后给每个点赋予一个随机实数权值,求所有新点权值大于其邻居的概率。

那么转移方程就对应着枚举最大值,除以的 \(n\) 其实对应 \((n-1)P+n\)

这个问题可以通过容斥解决,我们任取一个结点作为根,保留新点权值大于其儿子的限制,对新点权值大于其父亲的限制容斥。可以发现树分成了若干个互不影响的子连通块。(其结构是一棵有向树)

根据经典拓扑序结论,概率是每个点子树大小的倒数之乘积。于是令 \(f_{i,j}\) 表示 \(i\) 下面挂的连通块大小为 \(j\) 的方案数做树上背包即可。

复杂度 \(O(n^2)\)

代码

#include<stdio.h>
#include<vector>
using namespace std;
const int maxn=5005,mod=998244353;
int n,ans;
int sz[maxn],f[maxn][maxn],inv[maxn],g[maxn];
vector<int>v[maxn];
void dfs(int x,int last){
	sz[x]=1,f[x][1]=1;
	for(int t=0;t<v[x].size();t++){
		int y=v[x][t];
		if(y==last)
			continue;
		dfs(y,x);
		int sum=0;
		for(int i=1;i<=sz[y];i++)
			sum=(sum+f[y][i])%mod;
		for(int i=1;i<=sz[x];i++)
			for(int j=1;j<=sz[y];j++)
				g[i+j]=(g[i+j]+1ll*f[x][i]*f[y][j])%mod;
		sz[x]+=sz[y];
		for(int i=0;i<=sz[x];i++)
			f[x][i]=(1ll*f[x][i]*sum-g[i]+mod)%mod,g[i]=0;
	}
	for(int i=1;i<=sz[x];i++){
		f[x][i]=1ll*f[x][i]*inv[i]%mod;
		if(last!=0)
			f[x][i]=1ll*f[x][i]*inv[i]%mod;
	}
}
int main(){
	scanf("%d",&n),inv[1]=1;
	for(int i=2;i<=n;i++)
		inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
	for(int i=1,x,y;i<n;i++)
		scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	dfs(1,0);
	for(int i=1;i<=n;i++)
		ans=(ans+f[1][i])%mod;
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-09-07 21:19  xiaoziyao  阅读(138)  评论(0编辑  收藏  举报