1. 程式人生 > 其它 >Solution -「多校聯訓」博弈

Solution -「多校聯訓」博弈

\(\mathcal{Description}\)

  Link.

  A B 兩人在樹上博弈,初始時有一枚棋子在結點 \(1\)。由 A 先操作,兩人輪流移動沿樹上路徑棋子,且滿足本次移動的樹上距離嚴格大於上次的,無法移動者負。先給定一棵含 \(n\) 個結點的樹,求包含結點 \(1\) 且使得 B 必勝的聯通塊數量。

  \(n\le2\times10^5\)

\(\mathcal{Solution}\)

  結論對了正解寫了細節萎了暴力分都沒了 qwq……

  結論:聯通塊滿足條件,當且僅當其中最深的葉子們不同時屬於結點 \(1\) 的某棵子樹。當然也能說作,結點 \(1\) 是聯通塊直徑的中點。

證:略。

  “不同時屬於”不太優美,考慮用所有方案減去非法方案。對於結點 \(1\) 的每棵子樹,分別做長剖求出 \(f(v,d)\) 表示以 \(v\) 為根,最深葉子深度恰為 \(d\) 的聯通塊個數。隨後欽定某棵子樹取 \(d\),其餘子樹取嚴格小於 \(d\) 的方案,即能求出答案。

  複雜度\(\mathcal O(n)\)(其實我寫得比較難看,會有一個求逆元的 \(\log\) awa……)

\(\mathcal{Code}\)

/* Clearink */

#include <list>
#include <cstdio>

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

typedef std::pair<int, int> PII;
#define fi first
#define se second

inline int rint() {
    int x = 0, s = getchar();
    for ( ; s < '0' || '9' < s; s = getchar() );
    for ( ; '0' <= s && s <= '9'; s = getchar() ) x = x * 10 + ( s ^ '0' );
    return x;
}

const int MAXN = 2e5, MOD = 998244353;
int n, ecnt, head[MAXN + 5];
int mxd[MAXN + 5], son[MAXN + 5];
PII *top, pool[MAXN * 5 + 10], *f[MAXN + 5];
// second 是乘法標記,轉移過程中涉及到對長鏈的字尾乘法,需要打標記。
// 其實呢,由於出題人*****,更新時直接遍歷長鏈也能過√
struct Edge { int to, nxt; } graph[MAXN * 2];
std::list<int> rt;

inline int sub( int a, const int b ) { return ( a -=  b ) < 0 ? a + MOD : a; }
inline int add( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
inline int mul( const long long a, const int b ) { return int( a * b % MOD ); }
inline int mpow( int a, int b ) {
    int ret = 1;
    for ( ; b; a = mul( a, a ), b >>= 1 ) ret = mul( ret, b & 1 ? a : 1 );
    return ret;
}

inline void link( const int u, const int v ) {
    graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
    graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
}

inline PII* alloc( const int len ) {
    PII* ret = top; top += len + 3;
    return ret;
}

inline int init( const int u, const int fa ) {
    int ret = 1;
    mxd[u] = son[u] = 0;
    for ( int i = head[u], v; i; i = graph[i].nxt ) {
        if ( ( v = graph[i].to ) != fa ) {
            ret = mul( ret, add( init( v, u ), 1 ) );
            if ( mxd[u] < mxd[v] + 1 ) mxd[u] = mxd[son[u] = v] + 1;
        }
    }
    // printf( "! all(%d)=%d\n", u, ret );
    return ret;
}

inline void pushad( const int u, const int i, const int v ) {
    f[u][i].fi = mul( f[u][i].fi, v );
    if ( f[u][i].se ) f[u][i].se = mul( f[u][i].se, v );
    else f[u][i].se = v;
}

inline void pushdn( const int u, const int i ) {
    if ( f[u][i].se ) {
        if ( i < mxd[u] ) pushad( u, i + 1, f[u][i].se );
        f[u][i].se = 0;
    }
}

inline void solve( const int u, const int fa, PII* curf ) {
    f[u] = curf != NULL ? curf : alloc( mxd[u] + 1 );
    f[u][0].fi = 1;
    if ( son[u] ) solve( son[u], u, f[u] + 1 );
    pushdn( u, 0 );
    for ( int i = head[u], v; i; i = graph[i].nxt ) {
        if ( ( v = graph[i].to ) != fa && v != son[u] ) {
            solve( v, u, NULL );
            int vpre = 0, upre = 1;
            rep ( j, 0, mxd[v] ) {
                pushdn( v, j ), pushdn( u, j + 1 );
                if ( j <= mxd[v] ) vpre = add( vpre, f[v][j].fi );
                int t = f[u][j + 1].fi;
                f[u][j + 1].fi = add( mul( upre, f[v][j].fi ),
                  mul( f[u][j + 1].fi, add( vpre, 1 ) ) );
                upre = add( upre, t );
            }
            if ( mxd[v] + 2 <= mxd[u] ) {
                pushad( u, mxd[v] + 2, add( vpre, 1 ) );
            }
        }
    }
}

inline void allClear() {
    // mxd, son cleared in init.
    ecnt = 0;
    rep ( i, 1, n ) head[i] = 0;
    for ( PII *p = pool; p <= top; *p++ = {} );
    top = pool;
}

int main() {
    freopen( "game.in", "r", stdin );
    freopen( "game.out", "w", stdout );

    for ( int T = rint(); T--; ) {
        n = rint(), allClear();
        rep ( i, 2, n ) link( rint(), rint() );

        int all = init( 1, 0 ), del = 0;
        for ( int i = head[1], v; i; i = graph[i].nxt ) {
            solve( v = graph[i].to, 1, NULL );
            rt.push_back( v );
            rep ( j, 0, mxd[v] - 1 ) pushdn( v, j );
        }

        // printf( "! all=%d\n", all );

        int las = 1;
        rep ( i, 0, mxd[1] - 1 ) {
            for ( int u: rt ) {
                int k = !i ? 1 :
                  mul( las, mpow( add( f[u][i - 1].fi, 1 ), MOD - 2 ) );
                del = add( del, mul( k, f[u][i].fi ) );
                if ( i ) f[u][i].fi = add( f[u][i].fi, f[u][i - 1].fi );
            }
            for ( auto it( rt.begin() ); it != rt.end(); ) {
                if ( i ) {
                    las = mul( las,
                      mpow( add( f[*it][i - 1].fi, 1 ), MOD - 2 ) );
                }
                las = mul( las, add( f[*it][i].fi, 1 ) );
                if ( mxd[*it] == i ) it = rt.erase( it );
                else ++it;
            }
        }

        printf( "%d\n", sub( all, del ) );
    }
    return 0;
}

\(\mathcal{Details}\)

  也不是說程式碼能力差吧,為什麼我總能寫那麼長呢。

  賽時程式碼比較猙獰(?)可以理解,但儘量先細緻地過一遍程式碼流程,儘量寫下來,確認無誤再動手。