2021 陕西省赛 D - Disease // 树形概率dp

题目来源:2021年ICPC国际大学生程序设计竞赛暨陕西省第九届大学生程序设计竞赛

题目链接:https://ac.nowcoder.com/acm/contest/35232/D

题意

给定一棵根为 \(1\),大小为 \(n\) 的树:树上每个结点 \(i\) 初始有 \(\frac{\large p_i}{\large q_i}\) 的概率从外界染病,对于每条边 \(\{ u_j, v_j, a_j,b_j \}\),表示 \(u_j\)\(v_j\) 相连,且当其中一个点染病时,另一个点被传染的概率为 \(\frac{\large a_j}{\large b_j}\) .

定义灾难等级为:染病结点的最小深度(根节点 \(1\) 的深度为 \(1\))。

求:灾难等级的期望,答案对 \(10^9 + 7\) 取模。

数据范围:\(1 \le n \le 10^5\)\(0 \le p_i,q_i,a_j,b_j \le 10^6\).

思路:树形概率dp

考虑枚举灾难等级,即存在染病结点的最小深度 \(i\),若其概率为 \(g_i\),那么其对答案的贡献为 \(i \times g_i\) .

于是我们需要计算出 \(g_i\)

\(g_i\) 的事件实际上是,前 \(i-1\) 层不存在染病结点,且第 \(i\) 层存在染病结点。

更进一步分解为,前 \(i-1\) 层的结点初始均不染病(记为\(P_1\)),第 \(i\) 层存在自己染病但未传染给父亲的结点(记为\(P_2\)),即 \(g_i = P_1 \times P_2\) .

容易得到,\(P_1 = \prod\limits_{depth_{\large j} < i} (1-\frac{\large p_j}{\large q_j})\) .

对于\(P_2\) 的事件,其等价于:第 \(i\) 层的结点均不是自己染病并传染给父亲(即每个结点要么不染病,要么染病但不传染给父亲),并且,并非所有结点都不染病。则有,\(P_2 = \prod\limits_{depth_{\large j} = i} (1 - f_j \times \frac{\large a_j}{\large b_j})\ - \prod\limits_{depth_{\large j} = i}f_j\) ,其中:\(f_j\) 为 每个结点 \(j\) 最终不染病的概率,\(\frac{\large a_j}{\large b_j}\) 为 结点 \(j\) 传染给父亲的概率。

于是我们还需要先计算出 \(f_j\)

在我们计算 \(g_i\) 时,实际上限定了前 \(i-1\) 层的结点均初始不染病,因此我们是不需要考虑往下的传染的,只需要考虑往上的传染。

那么,\(f_j\) 的事件就是,结点 \(j\) 初始不染病,并且每个儿子都是要么不染病要么染病且不传染。

于是有,\(f_j = (1 - \frac{\large p_j}{\large q_j}) \times \prod\limits_{k\ \in\ son_{\large j}}(f_k + (1-f_k)·(1-\frac{\large a_k}{\large b_k}))\),其中:\(\frac{\large a_k}{\large b_k}\) 为 结点 \(k\) 传染给其父亲 \(j\) 的概率。

总的来说,需要先做一个树形dp求出 \(f_j\),再按层数由小到大求出 \(g_i\),并统计其对答案的贡献 \(i \times g_i\),最终答案为:

\(ans\ = \sum\limits_{i=1}^{maxDepth}i\ \times \prod\limits_{depth_{\large j} < i} (1-\frac{\large p_j}{\large q_j}) \times (\prod\limits_{depth_{\large j} = i} (1 - f_j · \frac{\large a_j}{\large b_j})\ - \prod\limits_{depth_{\large j} = i}f_j)\) .

时间复杂度:\(O(nlogP)\)\(P = 10^9 + 7\).

代码

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;

const int N = 100010;
const int mod = 1e9 + 7;

int n, dep[N], f[N], p[N], q[N];
vector<pair<int,int>> g[N];
vector<int> vec[N];

int qmi(int m, int k, int p) 
{
    int res = 1;
    while(k) {
        if(k & 1) res = res * m % p;
        m = m * m % p;
        k >>= 1;
    }
    return res;
}

int getmod(int x) 
{
    return (x % mod + mod) % mod;
}

int dfs(int u, int fa) {
    if(~f[u]) return f[u];
    vec[dep[u]].push_back(u);
    f[u] = getmod(1 - p[u]);
    for(auto e : g[u]) {
        int v = e.first, w = e.second;
        if(v == fa) continue;
        dep[v] = dep[u] + 1, q[v] = w;
        int p1 = dfs(v, u);
        int p2 = getmod(1 - dfs(v, u)) * getmod(1 - w) % mod;
        f[u] = f[u] * (p1 + p2) % mod;
    }
    return f[u];
}

signed main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);

    cin >> n;
    for(int i = 1; i <= n; ++ i) {
        int x, y;
        cin >> x >> y;
        p[i] = x * qmi(y, mod - 2, mod) % mod;
    }
    for(int i = 1; i < n; ++ i) {
        int u, v, a, b;
        cin >> u >> v >> a >> b;
        int w = a * qmi(b, mod - 2, mod) % mod;
        g[u].push_back({v, w}), g[v].push_back({u, w});
    }

    memset(f, -1, sizeof f);
    dep[1] = 1, dfs(1, -1);

    int ans = 0, P = 1;
    for(int i = 1; i <= n; ++ i) {
        if(!vec[i].size()) break;
        int res = P, P1 = 1, P2 = 1;
        for(auto x : vec[i]) {
            P = P * getmod(1 - p[x]) % mod;
            P1 = P1 * getmod(1 - getmod(1 - f[x]) * q[x] % mod) % mod;
            P2 = P2 * f[x] % mod;
        }
        res = res * getmod(P1 - P2) % mod;
        ans = (ans + res * i) % mod;
    }
    cout << ans << endl;

    return 0;
}
posted @ 2022-11-26 02:13  Jakon  阅读(69)  评论(0)    收藏  举报