• 「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 <cstdio>
    
    #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<typename _T>
    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<typename _T>
    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;
    }
    
  • 相关阅读:
    hdu 4622 Reincarnation 字符串hash 模板题
    NYOJ 994 海盗分金 逆向递推
    hdu 4679 Terrorist’s destroy 树形DP
    Educational Codeforces Round 12 E. Beautiful Subarrays 预处理+二叉树优化
    hdu 5535 Cake 构造+记忆化搜索
    poj 3415 Common Substrings 后缀数组+单调栈
    poj 3518 Corporate Identity 后缀数组->多字符串最长相同连续子串
    poj 2774 Long Long Message 后缀数组LCP理解
    hdu 3518 Boring counting 后缀数组LCP
    poj 3641 Pseudoprime numbers Miller_Rabin测素裸题
  • 原文地址:https://www.cnblogs.com/crashed/p/15642275.html
Copyright © 2020-2023  润新知