[CTS2019]氪金手遊 概率Dp,樹形Dp,容斥原理

[CTS2019]氪金手遊

題目傳送門:
luogu

分析

先考慮一下那個奇怪的條件(都知道是哪個吧)
它實際上是說,整個結構形成了一棵樹。
但是這棵樹很奇怪,邊有順的也有反的。
先考慮全是順的邊的情況,也就是說,對於這棵樹,子樹根得是第一個選到的。
假設所有的ww是定的,總的ww和是SumSum,子樹的ww和是SwS_w,子樹根的wwxx
那麼子樹的第ii次抽到的概率可以表示爲
xSum(1SwSum)i1\frac{x}{Sum}(1-\frac{S_w}{Sum})^{i-1}
總的概率就是
xSumi=0(1SwSum)i=xSumSumSw=xSw\frac{x}{Sum}\sum_{i=0}^{\infty}(1-\frac{S_w}{Sum})^i=\frac{x}{Sum}\frac{Sum}{S_w}=\frac{x}{S_w}
於是我們發現了一個重要的性質:一個節點能否滿足的概率是獨立的。這是這道題的第一個難點。
這樣子的話就可以DpDp了wa。設f[i][j]f[i][j]表示子樹ii總權值和爲jjii是第一個抽到的概率。
子樹合併轉移方程:hk=xkp,qfpgq[p+q==k]h_k=\frac{x}{k}\sum_{p,q}f_pg_q[p+q==k]
這個時候我們得考慮一件麻煩的事情,就是有反向邊的存在。
一種暴力的思路是容斥。
總樹滿足的方案等於1P(1滿)+P(2滿)1-P(至少1條不滿足)+P(至少2條不滿足)\cdots
假設我們暴力枚舉所有不滿足的反向邊,那麼Dp的時候相當於是把這些反向邊直接斷開形成一個森林然後Dp。最後把所有根的概率乘法原理。
考慮能不能把這個過程放回子樹Dp裏面,事實上一顆子樹的貢獻的正負取決於其有多少條反向邊不滿足。
容易想到多開一維f[i][j][k]f[i][j][k]kk表示子樹內有多少條邊沒有滿足
轉移的時候分類一下即可。
實際上更具體一點,我們僅僅關心kk的奇偶性,這樣狀態可以合併到f[i][j][0/1]f[i][j][0/1]
這個複雜度其實已經可以接受了。但是有一種更加簡單的做法。考慮一反向條是否滿足,如果滿足,那麼相當於是斷邊然後乘上概率。這個時候子樹合併的時候直接乘法原理即可。如果不滿足,那麼子樹內不同奇偶性的貢獻全部乘上-1,也就是總概率乘上-1即可。具體實現可以看代碼。
本題的兩個難點:1.轉化概率,2.容斥。其實都蠻套路的。前者要注意數學模型轉化,後者要注意先暴力分析,再貢獻合併分析。

分析

#include<bits/stdc++.h>
const int N = 1e3 + 10, P = 998244353;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int to[N << 1], nx[N << 1], w[N << 1], a[N], b[N], c[N], s[N], pr[N], sz[N], inv[N * 3], f[N][N * 3], tp, n;
long long g[N * 3];
void add(int u, int v, int _w) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp; w[tp] = _w;}
void adds(int u, int v) {add(u, v, 0); add(v, u, 1);}
int Inv(int x) {
    int r = 1;
    for(int k = P - 2;k; x = 1LL * x * x % P, k >>= 1)
        if(k & 1)
            r = 1LL * r * x % P;
    return r;
}
void Dp(int u, int fa) {
    sz[u] = 3;
    f[u][1] = 1LL * a[u] * s[u] % P;
    f[u][2] = 2LL * b[u] * s[u] % P;
    f[u][3] = 3LL * c[u] * s[u] % P;
    for(int i = pr[u]; i;i = nx[i])
        if(to[i] != fa) {
            Dp(to[i], u);
            for(int k = 1;k <= sz[u]; ++k)
                for(int j = 1;j <= sz[to[i]]; ++j) {
                    int res = 1LL * f[u][k] * f[to[i]][j] % P;
                    if(w[i])
                        g[k + j] -= res, g[k] += res;
                    else g[k + j] += res;
                }
            sz[u] += sz[to[i]];
            for(int j = 1;j <= sz[u]; ++j)
                f[u][j] = g[j] % P, g[j] = 0;
        }
    for(int i = 1;i <= sz[u]; ++i)
        f[u][i] = 1LL * f[u][i] * inv[i] % P;
}
int main() {
    n = ri(); inv[1] = 1;
    for(int i = 2;i <= n * 3; ++i)
        inv[i] = -1LL * inv[P % i] * (P / i) % P;
    for(int i = 1;i <= n; ++i) {
        a[i] = ri(); b[i] = ri(), c[i] = ri();
        s[i] = Inv((0LL + a[i] + b[i] + c[i]) % P);
    }
    for(int i = 1;i < n; ++i) {
        int u = ri(), v = ri();
        adds(u, v);
    }
    Dp(1, 0);
    long long ans = 0;
    for(int i = 1;i <= sz[1]; ++i)
        ans += f[1][i];
    printf("%d\n", (ans % P + P) % P);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章