Jzoj5444【NOIP2017提高A组冲刺11.2】救赎

“是的。”我回答,“我不会忘记你。在森林里我会一点点记起往日的世界。要记起的大概很多很多:各种人、各种场所、各种光、各种歌曲……”
——村上春树《世界尽头与冷酷仙境》
 
在没有心存在的世界尽头,音乐能够使小镇居民消散的心重新聚拢成形。作为镇子里唯一一个还残留着些许音乐记忆的人,我逐渐记起了往昔点滴……
 

记忆中有一棵无根树,有n个节点。
对于一棵有根树的每一个非叶子节点,我们都等概率选中其一个儿子节点作为偏好儿子。对于一条从父亲指向儿子的树边(u,v),如果v是u的偏好儿子,则称这条边为重边,否则为轻边。
我们定义一棵有根树的权值为其每一个节点到根路径上的轻边条数的和的期望值。

请对无根树每一个节点输出其为根的有根树的权值。答案模998244353。

一个十分明显的期望dp

我们先考虑一个根的结果,设f[x]表示子树x的答案,sz[x]表示子树x大小,k[x]表示x的儿子个数

那么 f[x]=

1.0 (k[x]=0)

2.(k-1)*(sz-1)/k+Σf[v] 

为什么?十分显然每个子树作为重儿子的可能性是一样的,而代价之和为sz[x]-1

让后我们考虑计算最后的答案g[x]

令p为x的父亲,我们假设g[p]已经计算出

那么显然,我们要将g[p]去掉f[x]对它的贡献,也就是

g[x]=f[x]+g[p]-(x对p的贡献)-(x的子树的贡献)+整颗树的贡献

其中,x对p的贡献=(n-1)*k[p]/(k[p]+1)-(n-sz[x]-1]*(k[p]-1)/k[p]

子树贡献=(k[x]-1)*(sz[x]-1)/k[x]

总贡献=(k[x])*(n-1)/(k[x]+1)

逆元什么的用线性预处理就好了

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#define N 100010
#define M 998244353
#define LL long long
using namespace std;
inline void ad(LL& x,LL y){ x=(x+y)%M; }
inline LL pow(LL x,LL k,LL s=1){
	for(;k;x=x*x%M,k>>=1) if(k&1) s=s*x%M;
	return s;
}
vector<int> s[N]; int n,sz[N],k[N]; LL f[N],g[N],inv[N];
void dfs(int x,int p){
	sz[x]=f[x]=k[x]=0;
	for(int v,i=0,z=s[x].size();i<z;++i)
		if(s[x][i]!=p){
			dfs(v=s[x][i],x); ++k[x];
			sz[x]+=sz[v]; f[x]+=f[v];
		}
	ad(f[x],(k[x]-1)*sz[x]%M*inv[k[x]]%M); ++sz[x];
}
void dgs(int x,int p){
	if(p){
		LL f1=0;
		ad(f1,g[p]-f[x]-(n-1)*(k[p])%M*inv[k[p]+1]%M+(n-sz[x]-1)*(k[p]-1)%M*inv[k[p]]%M);
		ad(f[x],M+f1-(k[x]-1)*(sz[x]-1)%M*inv[k[x]]%M); 
		g[x]=(M+f[x]+(k[x])*(n-1)%M*inv[k[x]+1]%M)%M;
	}
	for(int v,i=0,z=s[x].size();i<z;++i)
		if(s[x][i]!=p) dgs(s[x][i],x);
}
int main(){
	freopen("redemption.in","r",stdin);
	freopen("redemption.out","w",stdout);
	scanf("%d",&n);
	inv[0]=0; inv[1]=1;
	for(int i=2;i<=n+100;++i) inv[i]=(M-M/i)*inv[M%i]%M;
	for(int x,y,i=1;i<n;++i){
		scanf("%d%d",&x,&y);
		s[x].push_back(y);
		s[y].push_back(x);
	}
	dfs(1,0); g[1]=f[1]; --k[1]; dgs(1,0);
	for(int i=1;i<=n;++i) printf("%d
",(M+g[i])%M);
}

原文地址:https://www.cnblogs.com/Extended-Ash/p/7774311.html