AGC058F

忽略转移式中的 \(\frac{1}{n}\) ,形式比较像边分治,每次断一条边,求方案数。
由此可以联想到给每条边记录其时间戳。

考虑去除 \(\frac{1}{n}\) 的影响,将边拆成点,由于 \(\frac1n \equiv \frac{1}{n + (n-1)P} \pmod P\) ,原图点数为 \(n\) ,让每条边新增的点数为 \(P\) 即可,即每条边拆出的点下在连 \(P-1\) 个点。
那么问题变成了给每个点一个随机权值,边点的权值比相邻点都大的概率。

如何计算?
此时的图是一棵有向树,每条边指向较大点,对于 外向树/内向树 答案为 \(\prod\frac{1}{siz_x}\) ,以向上的方向为正,需要特殊处理向下的边。
如果只有一条边可以考虑容斥,即为 两部分概率乘积 - 该边正向时概率。
两部分的贡献都是与子树大小有关的,所以可以放到 DP 里面做,设 \(f_{i,j}\) 为以 \(i\) 为的内向树子树大小为 \(j\) ,转移即为树上背包。

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

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,a,b) for(int i=a;i>=b;--i)
typedef long long ll;
using namespace std;
const int N=5010, mod=998244353;
int n,ans,f[N][N],g[N],siz[N],inv[N],fac[N],ifac[N],fa[N];
vector<int> to[N];
int power(ll x,int n){
	ll a=1;
	while(n){
		if(n&1)a=a*x%mod;
		x=x*x%mod;
		n>>=1;
	}
	return a;
}
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
void init(){
	fac[0]=1;
	fo(i,1,n)fac[i]=(ll)fac[i-1] * i % mod;
	ifac[n]=power(fac[n],mod-2);
	fd(i,n,1)
		ifac[i-1]=(ll)ifac[i] * i % mod,
		inv[i]=(ll)ifac[i] * fac[i-1] % mod;
}
void dfs(int x){
	siz[x]=1;
	f[x][1]=1;
	for(auto y:to[x])if(y!=fa[x]){
		fa[y]=x;
		dfs(y);
		int sum=0;
		fo(i,1,siz[x]+siz[y])g[i]=0;
		fo(i,1,siz[y]){
			f[y][i]=(ll)f[y][i] * inv[i] % mod;
			sum=add(sum,f[y][i]);
		}
		fd(i,siz[x],1)
			fo(j,1,siz[y]){
				g[i+j]=((ll)f[x][i] * f[y][j] + g[i+j]) % mod;
			}
		siz[x]+=siz[y];
		fo(i,1,siz[x]){
			f[x][i]=((ll)f[x][i] * sum - g[i] + mod) % mod;
		}
	}
	fo(i,1,siz[x])f[x][i]=(ll)f[x][i] * inv[i] % mod;
}
int main(){
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	scanf("%d",&n);
	fo(i,2,n){
		int x,y;
		scanf("%d%d",&x,&y);
		to[x].push_back(y);
		to[y].push_back(x);
	}
	init();
	dfs(1);
	fo(i,1,n)ans=add(ans,f[1][i]);
	printf("%d\n",ans);

	return 0;
}
posted @ 2022-11-09 16:26  Kelvin2005  阅读(31)  评论(0)    收藏  举报