「CTS2019」氪金手游


题目

点这里看题目。

分析

不难发现题目给出的边的结构是一棵树。题目要求的是在有向边限制下,每张牌第一次出现构成的序列是这棵树的一种拓扑序的方案数。

首先,对于这类题目,一个经典的结论是:

\(i\) 张牌有 \(W_i\) 的概率被抽出来。那么对于 \(S\subseteq U,S\not=\varnothing\),一张牌 \(x\in S\)\(S\) 中第一个被抽出来的概率为:

\[\sum_{k\ge 0}\left(\frac{\sum_{z\in U}W_z-\sum_{y\in S}W_y}{\sum_{z\in U}W_z}\right)^k\cdot \frac{W_x}{\sum_{z\in U}W_z}=\frac{W_x}{\sum_{y\in S}W_y} \]

这个结论正向使用或者逆向使用都比较常见

一个想了半天的错误做法:

注意到最后答案只和 \(W\) 和一种拓扑序 \(\sigma\) 有关,我们可以直接写出答案:

\[\sum_\sigma\sum_W\left(\prod_{k=1}^np_{\sigma_k,W_k}\prod_{k=1}^nW_k\prod_{k=1}^n\left(\sum_{j=k}^nW_j\right)^{-1}\right) \]

然后可以提取公因数,外层枚举 \(W\),内层只需要枚举 \(\sigma\)。这个时候可能就可以开始 DP 了,只需要能够成功维护 \(\sum_\sigma \prod_k p_{\sigma_k,W_k}\) 即可,但是这个东西一点都不简单。

稍微研究一下这个不对的方向,我们发现,最大的错误出在没有联系树结构,而仅仅考虑了抽象的拓扑序 \(\sigma\)。现在我们应当回过头,重新审视一下“树”。

不妨从一个简单的情况开始考虑,我们可以完成“树是外向树”的问题吗?这个时候就要求,结点 \(u\) 必须早于它的所有儿子被取到。套用一下结论,如果 \(u\) 子树内包括 \(u\)\(\sum W\)\(S\),那么此时的概率就是 \(\frac{W_u}{S}\)。现在我们只需要简单地维护 \(dp_{u,s}\),表示 \(u\) 的子树内,\(S=s\) 的情况的概率之和即可。

那么,一般的树和上述情况的区别就在于,一般的树可以有反向边。但是,如果我们根本不管反向边的限制,那么原树就被划分为了若干个独立的连通块,分别计算并乘起来;此时多半会有不合反向边限制的情况,我们可以钦定某些反向边性质被打破,于是反向边就变成了正向边......不难发现,这个算法描述的正是一个容斥的过程。当然,我们大可不必枚举某些反向边云云,直接在遇到反向边的时候,修改 DP 的转移方程,把容斥算进去就可以了。

小结:

  1. 充分利用已知的结构。比如此题,拓扑序背后还是树结构,如果忽略树结构那么拓扑序本身就会变得非常晦涩;当然,也要学会掉头??。
    不过话说回来,在答案的式子比较容易写出来的时候,对着式子本身进行分析不失为一种有效的方法,只不过本题之中 \(\sigma\) 脱离了树之后就难以处理罢了。
  2. 考虑简单的子问题。先可以尝试 \(\sigma\) 唯一的情况,接着就可以尝试外向树的情况。最好不要忽略了某些特殊的情况,跨度太大思维难度也比较大。
  3. 关注一下提到的概率结论和反向边容斥的方法

代码

#include 

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int mod = 998244353; 
const int MAXN = 1e3 + 5;

template
void read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x; 
}

template
void write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) write( x / 10 );
    putchar( x % 10 + '0' ); 
}

struct Edge {
    int to, nxt;
}Graph[MAXN << 1];

int tmp[MAXN * 3], inv[MAXN * 3];
int dp[MAXN][MAXN * 3];
int siz[MAXN];

int possi[MAXN][4];
int head[MAXN], cnt = 1;

int N;

inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }

inline int Qkpow( int base, int indx ) {
    int ret = 1;
    while( indx ) {
        if( indx & 1 ) ret = Mul( ret, base );
        base = Mul( base, base ), indx >>= 1;
    }
    return ret;
}

inline void AddEdge( const int from, const int to ) {
    Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
    head[from] = cnt;
}

void Init( const int n ) {
    inv[1] = 1;
    rep( i, 2, n ) inv[i] = Mul( inv[mod % i], mod - mod / i );
}

void DFS( const int u, const int fa ) {
    siz[u] = 1;
    rep( j, 1, 3 ) dp[u][j] = possi[u][j];
    for( int i = head[u], v ; i ; i = Graph[i].nxt )
        if( ( v = Graph[i].to ) ^ fa ) {
            DFS( v, u ); int s = siz[u] + siz[v];
            if( ! ( i & 1 ) ) {
                rep( j, 1, s * 3 ) tmp[j] = 0;
                rep( j, 1, siz[u] * 3 )
                    rep( k, 1, siz[v] * 3 )
                        tmp[j + k] = Add( tmp[j + k], Mul( Mul( dp[u][j], dp[v][k] ), Mul( j, inv[j + k] ) ) );
                rep( j, 1, s * 3 ) dp[u][j] = tmp[j];
            } else {
                rep( j, 1, s * 3 ) tmp[j] = 0;
                rep( j, 1, siz[u] * 3 )
                    rep( k, 1, siz[v] * 3 ) {
                        tmp[j + k] = Sub( tmp[j + k], Mul( Mul( dp[u][j], dp[v][k] ), Mul( j, inv[j + k] ) ) );
                        tmp[j] = Add( tmp[j], Mul( dp[u][j], dp[v][k] ) );
                    }
                rep( j, 1, s * 3 ) dp[u][j] = tmp[j];
            }
            siz[u] += siz[v];
        }
}

int main() {
    read( N );
    rep( i, 1, N ) {
        int su = 0;
        rep( j, 1, 3 )
            read( possi[i][j] ),
            su = Add( su, possi[i][j] );
        rep( j, 1, 3 )
            possi[i][j] = Mul( possi[i][j], Inv( su ) );
    }
    rep( i, 1, N - 1 ) {
        int u, v;
        read( u ), read( v );
        AddEdge( u, v ), AddEdge( v, u );
    }
    Init( N * 3 );
    DFS( 1, 0 );
    int ans = 0;
    rep( j, 1, 3 * N )
        ans = Add( ans, dp[1][j] );
    write( ans ), putchar( '\n' );
    return 0;
}