Luogu P6789 寒妖王
阿新 • • 發佈:2020-10-22
Luogu 月賽 F P6789 寒妖王 [* easy]
給定 \(n\) 個點 \(m\) 條邊的圖,第 \(i\) 條邊的權值為 \(w_i\),每條邊有 \(\frac{1}{2}\) 的概率被保留,求最後這張圖的最大基環樹森林的邊權和。
Solution
\(\mathcal O(2^m\cdot m\alpha(n))\) 是顯然的。
類比一下 Kruskal 求 MST 就會做了。
考慮優化,我們不妨考慮判定一條邊什麼情況下不會被加入答案,等價於僅考慮邊權大於他的邊,有他連線的兩個點:
- 連通,且此時不為樹。
- 不連通,此時這兩個點分別在一個基環樹上。
第一類貢獻很簡單,我們統計僅保留這些邊時點集 \(S\)
統計 \(T\to T\land S\) 中的邊數通過 \(cnt_S-cnt_T-cnt_{T\land S}\) 來計算,這樣複雜度為 \(\mathcal O(m\cdot 3^n)\),需要適當減枝,比如點集 \(S\) 的邊數小於 \(|S|-1\) 時就直接 out
接下來考慮統計第二類貢獻。
不難發現第二類貢獻等價於點集 \(A\) 和點集 \(B\) 滿足 \(x\in A,y\in B\) 且 \(A\) 是聯通圖不是生成樹,\(B\)
列舉 \(A,B\) 的總量是 \(3^n\) 的,這樣只需要預處理聯通圖計數,經典的容斥手段可以做到 \(\mathcal O(3^n)\)
最後,我們在 \(\mathcal O(m3^n)\) 的複雜度解決了此問題。
- 預處理 \(cnt\) 陣列我是用高維字首和算的。
\(Code:\)
#include<bits/stdc++.h> using namespace std ; #define Next( i, x ) for( register int i = head[x]; i; i = e[i].next ) #define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i ) #define drep( i, s, t ) for( register int i = (t); i >= (s); -- i ) #define re register #define int long long int gi() { char cc = getchar() ; int cn = 0, flus = 1 ; while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; } while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ; return cn * flus ; } const int P = 998244353 ; const int N = (1 << 16) + 5 ; int n, m, limit, bit[N], ip[N], fac[70], iv[35], f[N], g[N], cnt[N], Ans ; struct E { int u, v, w ; } e[100] ; bool cmp(E x, E y) { return x.w > y.w ; } int fpow(int x, int k) { int ans = 1, base = x ; while(k) { if(k & 1) ans = 1ll * ans * base % P ; base = 1ll * base * base % P, k >>= 1 ; } return ans ; } void inc(int &x, int y) { ((x += y) >= P) && (x -= P) ; } void solve(int x) { memset( cnt, 0, sizeof(cnt) ), memset( f, 0, sizeof(f) ), memset( g, 0, sizeof(g) ) ; rep( i, 1, x - 1 ) ++ cnt[(1 << e[i].u) | (1 << e[i].v)] ; for(re int k = 1; k <= limit; k <<= 1 ) rep( i, 0, limit ) if(i & k) cnt[i] += cnt[i ^ k] ; rep( S, 1, limit ) { if( bit[S] == 1 ) { f[S] = g[S] = 1 ; continue ; } if( cnt[S] < bit[S] - 1 ) { f[S] = 0 ; g[S] = 0 ; continue ; } int w = (1 << ip[S]), z = S ^ w ; f[S] = 0, g[S] = fac[cnt[S]] ; for(re int i = z; i; i = (i - 1) & z) { int u = i ^ S, t = cnt[S] - cnt[i] - cnt[u] ; f[S] = (f[S] + 1ll * f[i] * f[u] % P * t ) ; g[S] = (g[S] - g[u] * fac[cnt[i]] % P) ; } f[S] %= P, g[S] = (g[S] % P + P) % P ; f[S] = f[S] * iv[bit[S] - 1] % P ; } int ans = 0, u = (1 << e[x].u), v = (1 << e[x].v) ; rep( S, 1, limit ) g[S] = (g[S] % P - f[S] % P + P) % P ; rep( S, 1, limit ) { if( (!(S & u)) || (!(S & v)) ) continue ; int d = g[S] ; d = d * fac[cnt[limit ^ S]] % P ; ans = (ans + d) % P ; //S 聯通且不為樹,列舉 S,外部任意 } rep( A, 1, limit ) { if( (!(A & u)) || (A & v) ) continue ; int T = A ^ limit ; for(re int B = T; B; B = (B - 1) & T) { if( !(B & v) ) continue ; ans = (ans + 1ll * g[A] * g[B] % P * fac[cnt[limit ^ A ^ B]] % P) % P ; } } int ffv = (P + 1) / 2 ; ans = ans * fpow( fac[x], P - 2 ) % P, ans = (ffv - ans + P) % P ; Ans = (Ans + ans * e[x].w) % P ; } signed main() { n = gi(), m = gi() ; rep( i, 1, m ) e[i].u = gi() - 1, e[i].v = gi() - 1, e[i].w = gi() ; sort(e + 1, e + m + 1, cmp), iv[0] = fac[0] = 1 ; rep( i, 1, n ) iv[i] = fpow(i, P - 2) ; rep( i, 1, m ) fac[i] = fac[i - 1] * 2 % P ; limit = (1 << n) - 1 ; rep( i, 1, limit ) { bit[i] = __builtin_popcountll(i) ; rep( j, 0, n - 1 ) if((1 << j) & i) { ip[i] = j ; break ; } } rep( i, 1, m ) solve(i) ; cout << (long long)Ans << endl ; return 0 ; }