Solution -「多校聯訓」博弈
阿新 • • 發佈:2021-06-21
\(\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}\)
也不是說程式碼能力差吧,為什麼我總能寫那麼長呢。
賽時程式碼比較猙獰(?)可以理解,但儘量先細緻地過一遍程式碼流程,儘量寫下來,確認無誤再動手。