题目链接:[CTS2019] 氪金手游

题意:

给定一棵 $n$ 个节点的树,每条边定向。每个节点有一个权值 $w_i\in\{1,2,3\}$,$w_i=j$ 的概率为 $p_{i,j}$。

第 $i$ 个点在卡池中放 $w_i$ 个,每次等概率从卡池中取出一个点。求 $n$ 个点被首次抽出的顺序,满足树上边的方向的概率。

$1\le n\le 10^3$

考虑如果树的定向方式是一棵外向树该怎么做。发现合法的情况下,$n$ 个点被首次抽出的顺序构成树的一个拓扑序。但每个节点在卡池中个数可能不同,所以无法直接套用树的拓扑序的式子。

但是可以魔改,对于以 $i$ 为根的子树,要求 $i$ 首次抽出的时间比子树内任何节点首次抽出的时间要早。设 $\text{sub}(i)$ 表示 $i$ 的子树,则合法方案数应为:

$$ \dfrac{\dfrac{((\sum_{j\in\text{sub}(i)} w_j)-1)!}{(w_i-1)!\prod_{j\in\text{sub}(i),j\ne i} w_j!}}{\dfrac{(\sum_{j\in\text{sub}(i)} w_j)!}{\prod_{j\in\text{sub}(i)} w_j!}} $$

$$ =\dfrac{w_i}{\sum_{j\in\text{sub(i)}} w_j} $$

因此可以设 $\text{dp}_{i,j}$ 表示在 $i$ 的子树中,子树 $w$ 之和为 $j$ 的情况下,合法的概率。

再考虑非外向树的情形。考虑往外向树的情形进行转化,因此可以进行容斥。将 “内向” 转变为 “不定向” $-$ “外向”

对于一条内向边 $(x,\text{fa}_x)$,在考虑他不定向的情形的时候,则 $x$ 子树内的 $w$ 之和与 $\text{fa}_x$ 没有关系了,所以这个子树的贡献需要删去,表示不考虑这条边的限制。

对于一组方案,如果有 $k$ 条内向边被我们设置为外向,则意味着至少 $k$ 条内向边被设置为外向。因为有一些被设置为不定向的内向边也可能是外向的。

因此,如果有 $k$ 条内向边被我们设置为外向,其容斥系数为 $(-1)^k$。

容易设出 $\text{dp}$。设 $\text{dp}_{i,j,k}$ 表示 $i$ 子树内,子树 $w$ 之和为 $j$,子树内有 $k$ 条内向边被设置为外向的情况下,合法的概率。

实际上,我们并不需要记录 $k$ 一维。因为我们发现,当我们在某一个状态上,多设置一条内向边为外向的时候,其容斥系数恰好 $\times (-1)$。所以我们只需要将这个 $-1$ 在 $\text{dp}$ 转移的时候乘上就行。

因此方程变为:设 $\text{dp}_{i,j}$ 表示 $i$ 子树内,子树 $w$ 和为 $j$ 的情况下,所有方案的概率乘上其容斥系数的和。容易使用背包转移的方式做到 $\mathcal{O}(n^2)$。

//Code By CXY07
#include<bits/stdc++.h>
using namespace std;

//#define FILE
#define int long long
#define file(FILENAME) freopen(FILENAME".in", "r", stdin), freopen(FILENAME".out", "w", stdout)
#define randint(l, r) (rand() % ((r) - (l) + 1) + (l))
#define LINE() cout << "LINE = " << __LINE__ << endl
#define debug(x) cout << #x << " = " << x << endl
#define abs(x) ((x) < 0 ? (-(x)) : (x))
#define inv(x) qpow((x), mod - 2)
#define lowbit(x) ((x) & (-(x)))
#define ull unsigned long long
#define pii pair<int, int>
#define LL long long
#define mp make_pair
#define pb push_back
#define scd second
#define vec vector
#define fst first
#define endl '\n'

const int MAXN = 3010;
const int INF = 2e9;
const double eps = 1e-6;
const double PI = acos(-1);
//const int mod = 1e9 + 7;
const int mod = 998244353;
//const int G = 3;
//const int base = 131;

int n, m, Ans;
int p[MAXN][4], siz[MAXN], Inv[MAXN];
int dp[MAXN][MAXN], tmp[MAXN];
vec<pii> G[MAXN];

template<typename T> inline bool read(T &a) {
    a = 0; char c = getchar(); int f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {a = a * 10 + (c ^ 48); c = getchar();}
    return a *= f, true;
}

template<typename A, typename ...B>
inline bool read(A &x, B &...y) {return read(x) && read(y...);}

int qpow(int x, int b) {
    int res = 1;
    for(; b; b >>= 1, (x *= x) %= mod) if(b & 1) (res *= x) %= mod;
    return res;
}

void DFS(int x, int f) {
    siz[x] = 3;
    for(int i = 1; i <= 3; ++i) dp[x][i] = p[x][i] * i % mod;
    for(auto to : G[x]) {
        if(to.fst == f) continue;
        DFS(to.fst, x);
        for(int j = 1; j <= siz[x]; ++j)
            for(int k = 1, coe; k <= siz[to.fst]; ++k) {
                coe = dp[x][j] * dp[to.fst][k] % mod;
                if(to.scd == 1) (tmp[j + k] += coe) %= mod;
                else (tmp[j + k] += mod - coe) %= mod, (tmp[j] += coe) %= mod;
            }
        siz[x] += siz[to.fst];
        for(int j = 1; j <= siz[x]; ++j) dp[x][j] = tmp[j], tmp[j] = 0;
    }
    for(int j = 1; j <= siz[x]; ++j) dp[x][j] = dp[x][j] * Inv[j] % mod;
}

signed main () {
#ifdef FILE
    freopen(".in", "r", stdin);
    freopen(".out", "w", stdout);
#endif
    read(n); Inv[1] = 1;
    for(int i = 2; i <= (n * 3); ++i)
        Inv[i] = Inv[mod % i] * (mod - mod / i) % mod;
    for(int i = 1, s; i <= n; ++i) {
        s = 0;
        for(int j = 1; j <= 3; ++j) read(p[i][j]), s += p[i][j];
        s = inv(s);
        for(int j = 1; j <= 3; ++j) p[i][j] = p[i][j] * s % mod;
    }
    for(int i = 1, x, y; i < n; ++i) {
        read(x), read(y);
        G[x].pb(mp(y, 1)), G[y].pb(mp(x, 0));
    }
    DFS(1, 0);
    for(int i = 1; i <= siz[1]; ++i) (Ans += dp[1][i]) %= mod;
    printf("%lld\n", Ans);
    return 0;
}

标签: 数学, 容斥, 概率

添加新评论