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=1∏nsizei(1−p)psizei−1+psizei
其中前一项 s i z e i ( 1 − p ) p s i z e i − 1 size_i(1-p)p^{size_i-1} sizei(1−p)psizei−1 表示子节点中恰好有一个非储存节点的情况,则共 s i z e i size_i sizei 种情况(每个子节点都可能是那个非储存节点),每种情况发生概率为 ( 1 − p ) p s i z e i − 1 (1-p)p^{size_i-1} (1−p)psizei−1 ( 1 − p 1-p 1−p 表示那个非储存节点的概率, p s i z e i − 1 p^{size_i-1} psizei−1 表示剩下的点均为储存节点的概率),第二项 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}
(1−p)psizefa−1 是恰好
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(1−p)psizefa−1+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(1−p)psizefa−1+psizefa(1−p)psizefa−1
(分母请用逆元处理)
最终答案:
a
n
s
=
P
∑
i
=
1
n
d
p
i
ans=P\sum^n_{i=1}dp_i
ans=Pi=1∑ndpi
(此处乘上 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;
}