題解-CTS2019氪金手遊

Problem

\(\mathtt {loj-3124}\)spa

題意概要:給定 \(n\) 個點,\(w_i\) 分別有 \(p_{i,1},p_{i,2},p_{i,3}\) 的機率取 \(1,2,3\)code

在肯定了全部的 \(w_i\) 後再開始遊戲:不斷抽點,點 \(i\) 被抽中的機率爲 \(\frac {w_i}{\sum_{j=1}^nw_j}\),直到全部點都被抽中過。遊戲

給定 \(n-1\) 個二元組 \((u,v)\) 表示第一次抽中 \(u\) 的時間須要比第一次抽中 \(v\) 的時間早,且若將這 \(n-1\) 個二元組中的兩個元素連無向邊,則這張圖是一棵樹。get

問知足全部二元組限制的機率。io

\(n\leq 1000\)class

Solution

(話說考場上看錯了題,覺得是肯定好每一個點抽中的機率後再進行遊戲,打了正解加仨暴力,跑出來都是同樣的就是和大樣例不同,致使心態崩了 並且題面裏並無提到這個區別方法

這題的解題突破口在於這個題的限制是一棵樹,但邊的方向不惟一(無法找到根使得這棵樹變成內向樹或外向樹)nw

爲了解題方便,不妨設這棵樹是以 \(1\) 號節點爲根的外向樹(對於每條邊而言,方向都是從靠近 \(1\) 的一端指向遠離 \(1\) 的一端)di

那麼能夠發現,\(1\) 號節點必須是最後一個抽到的,機率爲 \(\frac {w_1}{\sum_{i=1}^nw_i}\),並且只要知足這個條件後,這個點就沒有用了,問題分解成 \(1\) 的每棵子樹知足自身拓撲序的機率之積,這又是一個子問題時間

\(sz_i\) 表示 \(i\) 的子樹中機率係數的和(\(\sum_{v\in subtree(x)}w_v\)

即設 \(f[x]\) 表示 \(x\) 的子樹內知足拓撲序的機率,則 \(f[x]=\frac {w_x}{sz_x}\prod_{x\rightarrow v}f[v]\),不可貴到 \(Ans=f[1]=\prod_{x=1}^n\frac {w_x}{sz_x}\)

那麼能夠用一個Dp解決這個問題:設 \(f[x][i]\) 表示在 \(i\) 的子樹中、機率係數和爲 \(j\) 且知足拓撲序的機率,\(O(n^2)\)

毒瘤出題人竟然不給這檔部分分?


考慮到這棵樹不是外向樹,存在若干條內向邊。

直接處理內向邊很差處理,如今存在一種方法,即用 「不考慮這條邊」的方案數 減去 「考慮這條邊爲外向邊」的方案數

(用圖來表達即:\(o\rightarrow o\leftarrow o\) 等於 \(o\rightarrow o\quad o\) 減去 \(o\rightarrow o\rightarrow o\)

而後在樹上Dp時,遇到此類內向邊就將權值用上面這種方法計算一下便可。


據說這題還有另外一種作法,代碼實現上是同樣的,但思想不大同樣:

考慮設 \(g[i]\) 表示這若干條內向邊中,至少有 \(i\) 條內向邊的條件不知足,則容斥可知答案爲:\(\sum_i(-1)^ig[i]\)

而 「至少 \(i\) 條內向邊不知足」,也即 「\(i\) 條內向邊變爲外向邊,而其餘內向邊斷開」

這樣在Dp的時候加入一個容斥係數便可:一開始設全部內向邊斷開,而後給其加上一個負的 「內向邊變外向邊」 的權值

Code

//loj-3124
#include <cstdio>
typedef long long ll;

const int N = 1013, p = 998244353;
struct Edge {int v, w, nxt;} a[N<<1];
int head[N], sz[N];
int a1[N], a2[N], a3[N];
int F[N][N*3], arr[N*3];
int inv[N*3], n, _;

inline int qpow(int A, int B) {
    int res = 1; while(B) {
        if(B&1) res = (ll)res * A%p;
        A = (ll)A * A%p, B >>= 1;
    } return res;
}

void dfs(int x, int las) {
    int*f = F[x];
    f[0] = 1;
    for(int ii=head[x],v;ii;ii=a[ii].nxt)
        if((v=a[ii].v) != las) {
            dfs(v, x);
            int*g = F[v];
            const int sx = sz[x] * 3, sv = sz[v] * 3;
            for(int i=0;i<=sx+sv;++i) arr[i] = 0;
            if(!a[ii].w)
                for(int i=0;i<=sx;++i)
                for(int j=0;j<=sv;++j)
                    arr[i+j] = (arr[i+j] + (ll)f[i] * g[j])%p;
            else {
                ll sm = 0;
                for(int i=0;i<=sv;++i) sm += g[i];
                sm %= p;
                for(int i=0;i<=sx;++i) arr[i] = sm * f[i]%p;
                
                for(int i=0;i<=sx;++i)
                for(int j=0;j<=sv;++j)
                    arr[i+j] = (arr[i+j] - (ll)f[i] * g[j] + (ll)p*p)%p;
            }
            for(int i=0;i<=sx+sv;++i) f[i] = arr[i];
            sz[x] += sz[v];
        }
    const int sx = sz[x] * 3;
    for(int i=0;i<=sx+3;++i) arr[i] = 0;
    for(int i=0;i<=sx;++i) {
        arr[i+1] = (arr[i+1] + (ll)f[i] * a1[x]%p * inv[i+1])%p;
        arr[i+2] = (arr[i+2] + (ll)f[i] * a2[x]%p * inv[i+2] * 2)%p;
        arr[i+3] = (arr[i+3] + (ll)f[i] * a3[x]%p * inv[i+3] * 3)%p;
    }
    for(int i=0;i<=sx+3;++i) f[i] = arr[i];
    ++sz[x];
}

int main() {
    scanf("%d",&n);
    inv[0] = inv[1] = 1;
    for(int i=2;i<=n*3;++i) inv[i] = (ll)(p-p/i) * inv[p%i]%p;
    for(int i=1;i<=n;++i) {
        scanf("%d%d%d",a1+i,a2+i,a3+i);
        ll v = qpow(a1[i] + a2[i] + a3[i], p-2);
        a1[i] = v * a1[i]%p;
        a2[i] = v * a2[i]%p;
        a3[i] = v * a3[i]%p;
    }
    for(int i=1,x,y;i<n;++i) {
        scanf("%d%d",&x,&y);
        a[++_].v = y, a[_].w = 0, a[_].nxt = head[x], head[x] = _;
        a[++_].v = x, a[_].w = 1, a[_].nxt = head[y], head[y] = _;
    }
    dfs(1, 0);
    
    ll Ans = 0;
    for(int i=sz[1]*3;~i;--i)
        Ans += F[1][i];
    printf("%lld\n", Ans%p);
    return 0;
}
相關文章
相關標籤/搜索