2021牛客暑期多校训练营9 G题: Glass Balls

G题: Glass Balls

原题链接:https://ac.nowcoder.com/acm/contest/11260/G

题目大意

有一棵 n n n 个节点的树,根为 1 1 1 ,每个节点上有个玻璃球。
对于 v v v 的任意儿子 u u u u u u 的高度比 v v v 1 1 1 个单位,因此 u u u 上的玻璃球可以沿 ( u , v ) (u,v) (u,v) 边滚到 v v v 。球沿一条边滚动需要花费 1 1 1 秒。

所有球同时开始滚动。有一些节点是可储存节点,其中根节点必定是可储存节点,其他节点各自独立有 p p p 的概率是可储存节点。如果球滚到可储存节点就会立刻破裂,然后从树上取下。如果球初始就在可储存节点上,会立刻破裂。

两个球如果同时滚到同一个节点就会发生碰撞,不论节点是否是可储存节点。如果发生碰撞,不仅两个球会碎,整个系统也会崩坏,所有滚动都会停止。

如果发生碰撞,则分数为 0 0 0 否则分数为 ∑ i = 1 n f ( i ) \sum^n_{i=1}f(i) i=1nf(i) 其中 f ( u ) f(u) f(u) 表示初始在 u u u 上的球所滚过的边数。
求分数的期望。

题解

根据题目的描述,不难发现,对于一种合法(指不会出现碰撞)的情况,每个节点的子节点中(即互为兄弟的节点中)至多有一个不是储存节点。
我们设 s i z e x size_x sizex 表示 x x x 的子节点数量,易得所有情况中合法的概率为:
P = ∏ i = 1 n s i z e i ( 1 − p ) p s i z e i − 1 + p s i z e i P=\prod^n_{i=1}size_i(1-p)p^{size_i-1}+p^{size_i} P=i=1nsizei(1p)psizei1+psizei

其中前一项 s i z e i ( 1 − p ) p s i z e i − 1 size_i(1-p)p^{size_i-1} sizei(1p)psizei1 表示子节点中恰好有一个非储存节点的情况,则共 s i z e i size_i sizei 种情况(每个子节点都可能是那个非储存节点),每种情况发生概率为 ( 1 − p ) p s i z e i − 1 (1-p)p^{size_i-1} (1p)psizei1 ( 1 − p 1-p 1p 表示那个非储存节点的概率, p s i z e i − 1 p^{size_i-1} psizei1 表示剩下的点均为储存节点的概率),第二项 p s i z e i p^{size_i} psizei 表示子节点均为存储节点的概率(每个节点概率为 p p p ,共 s i z e i size_i sizei 个)。

接下来我们考虑如何计算期望。
我们设 d p x dp_x dpx 表示 f ( x ) f(x) f(x) 的期望值,显然,若 x x x 可以滚到它的父节点 f a fa fa (即 x x x 为非存储节点),则它的状态可以从父节点转移而来。因为从 x x x f a fa fa 已经走过了一条边,不难得到初步转移式 d p x = 1 + d p f a dp_x=1+dp_{fa} dpx=1+dpfa 。但是 x x x 不一定是一个合法的非储存节点,我们要乘上其概率系数,完整转移式如下( ( 1 − p ) p s i z e f a − 1 (1-p)p^{size_{fa}-1} (1p)psizefa1 是恰好 x x x 为非储存节点,其余节点为储存节点的概率, s i z e f a ( 1 − p ) p s i z e f a − 1 + p s i z e f a size_{fa}(1-p)p^{size_{fa}-1}+p^{size_{fa}} sizefa(1p)psizefa1+psizefa x x x 及其兄弟中( f a fa fa 的子节点中)总共的合法概率(可参考上文中 P P P 的计算式)):
d p x = ( 1 + d p f a ) ( 1 − p ) p s i z e f a − 1 s i z e f a ( 1 − p ) p s i z e f a − 1 + p s i z e f a dp_x=(1+dp_{fa})\frac{(1-p)p^{size_{fa}-1}}{size_{fa}(1-p)p^{size_{fa}-1}+p^{size_{fa}}} dpx=(1+dpfa)sizefa(1p)psizefa1+psizefa(1p)psizefa1

(分母请用逆元处理)

最终答案:
a n s = P ∑ i = 1 n d p i ans=P\sum^n_{i=1}dp_i ans=Pi=1ndpi

(此处乘上 P P P 的原因是 d p dp dp 的部分合法不一定能保证整体合法)

参考代码

#include<bits/stdc++.h>
#define For(i,n,m) for(int i=n;i<=m;i++)
#define FOR(i,n,m) for(int i=n;i>=m;i--)
#define Mod 998244353
using namespace std;
void read(int &x){int ret=0;char c=getchar(),last=' ';while(!isdigit(c))last=c,c=getchar();while(isdigit(c))ret=ret*10+c-'0',c=getchar(); x=last=='-'?-ret:ret;}
int pow(int x,int p){//快速幂
	int ret=1;
	for(;p;x=1ll*x*x%Mod,p>>=1)if(p&1)ret=1ll*ret*x%Mod;
	return ret;
}
int inv(int x){return pow(x,Mod-2);}//逆元

const int MAXN=5e5+5;
int n,p,P=1,f[MAXN],dp[MAXN],siz[MAXN];//f[i]记录i的父节点,dp[i]记录f(i)的期望值,siz[i]记录i的子节点数
vector<int>e[MAXN];//e[i]中存放i的子节点

void dfs(int x){//dfs递归遍历(其实循环1~n遍历也行)
	if(e[x].empty())return;//叶节点
	P=1ll*P*((1ll*siz[x]*(1-p+Mod)%Mod*pow(p,siz[x]-1)%Mod+pow(p,siz[x]))%Mod)%Mod;//计算P,注意取模
	int son;
	For(i,0,e[x].size()-1){//遍历子节点
		son=e[x][i];
		dfs(son);
	}
}

void sol(int x){//递归计算dp值(从根开始,因为只有先处理父节点的dp值,才能计算子节点的dp值)
	dp[x]=1ll*(1+dp[f[x]])*(1-p+Mod)%Mod*pow(p,siz[f[x]]-1)%Mod*inv(1ll*siz[f[x]]*(1-p+Mod)%Mod*pow(p,siz[f[x]]-1)%Mod+pow(p,siz[f[x]]))%Mod;//计算dp,注意取模
	if(e[x].empty())return;//叶节点
	int son;
	For(i,0,e[x].size()-1){//遍历子节点
		son=e[x][i];
		sol(son);
	}
}

int main()
{
	read(n),read(p);
	if(n==1){puts("0");return 0;}//只有根(根一定是储存节点),输出0
	For(i,2,n){
		read(f[i]);//读入父节点
		e[f[i]].push_back(i);//存入子节点
	}
	For(i,1,n)siz[i]=e[i].size();//
	dfs(1);//遍历计算P
	For(i,0,e[1].size()-1)sol(e[1][i]);//从1的每个子节点开始递归遍历计算dp值
	int ans=0;
	For(i,1,n)ans=(1ll*ans+dp[i])%Mod;//累加dp
	ans=1ll*ans*P%Mod;//乘上P
	printf("%d\n",ans);
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值