2021牛客暑期多校训练营9 G (树上概率dp 对于存在不合法的情况dp启示)

44 篇文章 0 订阅
41 篇文章 1 订阅
题目大意:

有一棵树,树上每个点都有一个球,每秒钟每个球都会向其父亲结点移动。除了跟结点之外,其余每个结点有p的概率为存储点,当球移到存储点则该球从树中删除另外同一秒钟一个点上不能有一个球以上,即使是存储点也一样,如果出现则整棵树不合法

解题思路:
  • 首先能够知道的是,如果整颗树是合法的,那么每个结点的子结点最多只有一个不是存储点
  • 那么设置 s c o r e [ u ] score[u] score[u]为当前结点能够得到的分数,则状态转移为: s c o r e [ u ] = p q ∗ ∑ v u s o n ( s c o r e [ v ] + 1 ) score[u]=pq*\sum_{v}^{uson}(score[v]+1) score[u]=pqvuson(score[v]+1) p q pq pq为当前 v v v不为存储点其余点 v ′ v' v为存储点的概率
  • 但是!还需要考虑到存在不合法的情况导致无法继续,所以要先把合法的总概率得到,再看pq在合法总概率中的占比才行,即 p q = p q 合 法 总 概 率 pq=\frac{pq}{合法总概率} pq=pq
  • 除此之外,还需要计算每个子树合法的概率,最后用总答案乘整颗树合法的概率才为答案
反思:
  • 做这道题的时候,笔者就是因为没有想到要在合法概率中考虑递推过程才导致始终无法算对,这个是一定要注意。此外最终要在这基础上考虑树的合法概率。
AC代码:
#include <bits/stdc++.h>
#define ft first
#define sd second
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define seteps(N) fixed << setprecision(N)
#define endl "\n"
const int maxn = 5e5 + 10;
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
const ll mod = 998244353;
ll qpow(ll a, ll b) {
   ll res = 1;
   while (b) {
       if (b & 1) res = res * a % mod;
       a = a * a % mod;
       b >>= 1;
   }
   return res;
}
vector <int> G[maxn];
int n;
ll p, prod[maxn], score[maxn];
void dfs(int u, int fu) {
    ll cnt = 0;
    for (auto v : G[u]) {
        if (v == fu) continue;
        dfs(v, u);
        cnt++;
    }
    if (!cnt) {
        prod[u] = 1; //无子节点,则合法概率就为1
        return;
    }
    ll pq = qpow(p, cnt - 1) % mod * (mod + 1 - p) % mod;//子节点有一个不是存储点概率
    ll pp = qpow(p, cnt);//子节点全是存储点概率
    ll mm = (pp + cnt * pq % mod) % mod; //计算总合法概率
    prod[u] = mm;
    pq = pq * qpow(mm, mod - 2) % mod; //重新计算pq
    for (auto v : G[u]) {
        if (v == fu) continue;
        prod[u] = prod[u] * prod[v] % mod;//计算当前子树的合法概率
        score[u] = (score[u] + pq * (score[v] + 1) % mod) % mod;
        //因为转移一步到u,贡献是score[v]再加上v结点的那1个球,所以是score[v]+1,因为当前score[v]是v的子树到v的贡献,并没有计算v结点的球
    }
}
ll ans = 0;
int main() {
    cin >> n >> p;
    for (int i = 2, j; i <= n; i++)
        cin >> j, G[i].push_back(j), G[j].push_back(i);
    dfs(1, 0);
    for (int i = 1; i <= n; i++)
        ans = (ans + score[i]) % mod;
    cout << ans * prod[1] % mod << endl;  //最后乘上一整棵树合法概率
}

/*
5 332748118
1 2 2 3

4 332748118
1 1 2

6 332748118
1 1 3 3 4
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值