CF1060F Shrinking Tree 题解

CF1060F Shrinking Tree

思路

主要是难以理清思维链条。看题解也感觉有点难理解。主要是状态设计的抽象性以及状态转移的隐性。

首先先概率转总和。我们考虑对于每一种删去边的方案去求其剩下的编号是 \(rt\)(每次钦定的根)的概率。这样两个互相影响的随机事件就变成了一个,方便统计。

我们将删去边的意义抽象成将两端的颜色合为同一种颜色。
\(f_{u,i}\) 表示对于 \(u\) 的子树,对于所有删完子树中的边的情况,只考虑其最后 \(i\) 次删去边的选择的颜色是什么,使得保证 \(u\) 点的颜色仍是其自己的概率。倒数第 \(i\) 次之前的随便取,我们认为其不重要。
那么答案就是 \(\frac {f_{rt,n-1}} {(n-1)!}\)

考虑如何合并答案。设 \(v\)\(u\) 的一个儿子。由于我们现在要转移 \(f_u\),因此我们就要考虑 \((u,v)\) 这条边选的什么颜色。

我们枚举一个 \(i,j\)。设 \(g_{v,i}\) 表示删 \(v\) 及其子树\(u\) 构成的树,只考虑删去最后 \(i\) 次删的边选择的颜色使得保证 \(u\) 的颜色仍然是其自己的概率。也就是与 \(f\) 类似的定义。

考虑 \(g_{v,i}\)\(f_{v,j}\) 转移。考虑分情况。如果 \(i<j\),那么显然这个时候根据 \(f\) 的定义已经保证了 \(u\) 的颜色,于是直接有 \(g_{v,i}\gets f_{v,i}\)。否则,就没有保证 \((u,v)\) 这条边选择的颜色,那么就有 \(g_{v,i}\gets \frac 1 2 f_{v,j}\)

然后考虑 \(g\)\(u\) 之前已经算完的子树的合并。由于我们只是规定了最后几个边删除的相对顺序,因此有

\[f_{u,i+j}\gets {i+j\choose i}{siz_u-i-1+siz_v-j\choose siz_u-i-1}f_{u,i}g_{v,j} \]

最后考虑第一个求 \(g\) 的转移可以用前缀和优化,第二部分是树上背包合并,总复杂度为 \(O(n^3)\)

code

这里没有写浮点数的版本,而是对 998244353 取模。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=505,p=998244353;
int add(const int &x,const int &y){return x+y<0?x+y+p:(x+y>p?x+y-p:x+y);}
void upd(int &x,const int &y){x=add(x,y);}
int ksm(int x,int k){
	int res=1;x%=p;while(k){
		if(k&1)res=res*x%p;
		x=x*x%p;k>>=1;
	}return res;
}
int inv(int x){return ksm(x,p-2);}
const int inv2=inv(2);
vector <int> q[N];
int n,f[N][N],g[N],siz[N],pw[N],ni[N],tmp[N],s[N];
int C(int x,int y){
	if(y>x)return 0;
	return pw[x]*ni[y]%p*ni[x-y]%p;
}
void dfs(int u,int fa){
	siz[u]=1;f[u][0]=1;
	for(int v:q[u]){
		if(v==fa)continue;dfs(v,u);memset(g,0,sizeof(g));
		for(int i=0;i<=siz[v];i++){
			if(i>=0)upd(g[i],inv2*s[i-1]%p);
			upd(g[i],(siz[v]-i)*f[v][i]%p);
		}
		memset(tmp,0,sizeof(tmp));
		for(int i=siz[u]-1;i>=0;i--){  //这种关于子树大小的树形背包一次的总复杂度是 n^2 的 
			for(int j=siz[v];j>=0;j--){
				upd(tmp[i+j],C(i+j,j)*C(siz[u]-i-1+siz[v]-j,siz[u]-i-1)%p*g[j]%p*f[u][i]%p);
			}
		}
		for(int i=0;i<siz[u]+siz[v];i++)f[u][i]=tmp[i];
		siz[u]+=siz[v];
	}
	s[0]=f[u][0];for(int i=1;i<=siz[u];i++)s[i]=add(s[i-1],f[u][i]);// 
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;for(int i=1,u,v;i<n;i++)cin>>u>>v,q[u].push_back(v),q[v].push_back(u);
	pw[0]=1;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*i%p;ni[n]=inv(pw[n]);
	for(int i=n-1;i>=0;i--)ni[i]=ni[i+1]*(i+1)%p;
	for(int i=1;i<=n;i++)memset(f,0,sizeof(f)),dfs(i,0),cout<<(f[i][n-1]*ni[n-1]%p)<<'\n';
	return 0;
}
posted @ 2025-08-18 21:17  all_for_god  阅读(23)  评论(0)    收藏  举报