2021牛客多校#9 G-Glass Balls(期望dp)

题目链接

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 的概率是可存储节点。如果球滚到可储存可存储节点就会立刻破裂,然后从树上取下。如果球初始就在可存储节点上,会立刻破裂。
两个球如果同时滚到同一个节点就会发生碰撞,不论节点是否为可存储节点。如果发生碰撞,不仅两个球会碎,整个系统也会崩坏,所有滚动都会停止。
如果发生碰撞,则分数为 ∑ i = 1 n f ( i ) \sum_{i=1}^nf(i) i=1nf(i),其中 f ( u ) f(u) f(u) 表示初始在 u u u 上的球所滚过的边数。
求分数的期望。

题解

由题意得,两个球在同一个节点出现就会崩坏,所以一个节点只能有一个或零个球经过,所以该节点不崩坏的概率为一个球 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加上零个 p s i z e i p^{size_i} psizei ( s i z e i (size_i (sizei表示第 i i i个节点的子节点数 ) ) )
所以整棵树的合法概率是所有节点相乘,即 P = ∏ i n s i z e i ∗ ( 1 − p ) p s i z e i − 1 + p s i z e i P=\prod_{i}^{n}size_i*(1-p)p^{size_i-1}+p^{size_i} P=insizei(1p)psizei1+psizei
我们设 d p i dp_i dpi为合法的 i i i 节点上球的期望,从根节点出发求每个节点的 d p dp dp值。
不是吧不是吧,有人期望不是倒着求的?
已知该节点所有合法的情况为 s i z e i ∗ ( 1 − p ) p s i z e i − 1 + p s i z e i size_i*(1-p)p^{size_i-1}+p^{size_i} sizei(1p)psizei1+psizei(设 j j j i i i的父节点)
能到 i i i的合法情况为 ( 1 − p ) ∗ p s i z e j − 1 (1-p)*p^{size_j-1} (1p)psizej1
同时从父节点能遗传到得它的信息。
所以总结可以得到 d p i = ( 1 + d p j ) ( 1 − p ) ∗ p s i z e j − 1 s i z e i ∗ ( 1 − p ) p s i z e i − 1 + p s i z e i dp_i=(1+dp_j)\frac{(1-p)*p^{size_j-1}}{size_i*(1-p)p^{size_i-1}+p^{size_i}} dpi=(1+dpj)sizei(1p)psizei1+psizei(1p)psizej1
最后将得到的 d p dp dp综合乘上合法总数 P P P
过程中需要运用逆元,快速幂,注意时刻取模。

参考代码

#include<bits/stdc++.h>
#define pb push_back
#define FOR(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
const int N=5e5+5;
const int mod=998244353;
int n,p;
int dp[N],siz[N],f[N];
long long s=0,P=1;
vector<int> v[N];
void read(int &x)   //快读
{
    int ret=0;
    char c=getchar();
    while(!isdigit(c))
        c=getchar();
    while(isdigit(c))
        ret=ret*10+c-'0',c=getchar();
    x=ret;
}
int powmod(int a,int b)     //快速幂
{
    int ret=1;
    while(b)
    {
        if(b&1)
            ret=1ll*ret*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ret;    
}
void dfs(int x)      //求所有节点合法概率P
{
    if(v[x].size()==0)
        return;
    FOR(i,0,v[x].size()-1)
        dfs(v[x][i]);
    P=1ll*P*((1ll*siz[x]*(1-p+mod)%mod*powmod(p,siz[x]-1)%mod+powmod(p,siz[x]))%mod)%mod;
}
void sol(int x)      //求每个节点的期望dp[x]
{
    dp[x]=1ll*(1+dp[f[x]])*(1-p+mod)%mod*powmod(p,siz[f[x]]-1)%mod*powmod(1ll*siz[f[x]]*(1-p+mod)%mod*powmod(p,siz[f[x]]-1)%mod+powmod(p,siz[f[x]]),mod-2)%mod;     //除法转逆元
    if(v[x].size()==0)
        return;
    FOR(i,0,v[x].size()-1)
        sol(v[x][i]);
}
int main()
{
    read(n);
    read(p);
    if(n==1)
    {
        puts("0");
        return 0;
    }
    FOR(i,2,n)
    {
        read(f[i]);            //读入父亲
        v[f[i]].push_back(i);   //构边
    }
    FOR(i,1,n)
        siz[i]=v[i].size();
    dfs(1);
    FOR(i,0,v[1].size()-1)         
        sol(v[1][i]);
    FOR(i,1,n)
        s=(s+dp[i])%mod;      //累加
    printf("%lld",s*P%mod);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值