「Luogu4321」隨機遊走
「Luogu4321」隨機遊走
題目描述
有一張 \(n\) 個點 \(m\) 條邊的無向圖,\(Q\) 組詢問,每次詢問給出一個出發點和一個點集 \(S\) ,求從出發點出發隨機遊走走遍這個點集的期望步數。\(1 \leq n \leq 18, 1 \leq Q \leq 10^5\)
解題思路 :
聽說是 \(\text{pkuwc2018d2t3}\) 加強版?但是原題時限是1s,各種卡不進去感覺一定要寫 \(\text{Min-Max}\) 容斥,不過反正我今年聽指導建議沒報 \(\text{pkuwc}\) ,結果 \(\text{thuwc}\) 沒 \(\text{py}\) 。
喪心病狂的出題人肯定是要我們 \(O(1)\) 回答詢問了,不過感覺暴力還是蠻好想的,網上以前看到有大佬說過期望要倒著推,然後狀壓一波就出來了。設 \(f(S, u)\) 表示當前已經走遍了點集 \(S\) 接下來走遍全圖的期望。這樣定義的好處在於終止狀態方便表示,直接認為點集外的點已經走過就可以了,如果正著定義走遍點集 \(S\) 的期望的話,終止狀態的計算就需要一堆集合再算上對應的概率了。於是顯然有
\[ f(S, u) = \frac{1}{deg_u} \sum_{edge(u, v)} f(S|v, v) + 1 \]
特別的 \(f(S, i) = 0\) 當 \(S\)
這個式子的轉移還有環誒,所以還要高斯消元來解,一眼看上去複雜度是 \(O((n\times2^n)^3)\),複雜度爆炸。
冷靜分析一下,並不是所有的轉移都會成環,當滿足 \(v \notin S\) 的時候,\(S\) 是 \(S|v\) 的真子集,只考慮這樣的轉移的話轉移的形態是一個 \(\text{DAG}\) ,更準確的說是一個分層圖。
這啟發我們可以來分層求解,把互不相交的環拆開來考慮,而不是一起高斯消元,不妨變換一下轉移的式子。
\[ f(S, u) = \frac{1}{deg_u} \sum_{edge(u, v), v \notin S} f(S|v, v) + \sum_{edge(u, v), v \in S} f(S, v) + 1 \]
此時成環的轉移只有後面那個式子,於是我們可以一層一層計算,對於每一個 \(S\) ,先將前面那個式子的貢獻記錄在矩陣裡面,暴力消元后面那個式子這樣每次消元的矩陣大小隻有 \(n^2\) 級別,總複雜度優化到 \(O(2^n\times n^3)。\)
另外吐槽一下:我的高斯消元寫法好像不加剪枝就被卡常數了,今天做了一天的題沒有一道不被卡常,怕是聯賽要被卡成暴力分。
/*program by mangoyang*/
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int ch = 0, f = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 25, mod = 998244353;
typedef int Matrix[N][N];
vector<int> g[N];
Matrix a; int Ans[(1<<20)+5][N], n, e;
inline void up(int &x, int y){ (x += y) %= mod; }
inline void del(int &x, int y){ x = (x + y >= 0) ? x + y : x + y + mod; }
inline int Pow(int a, int b){
int ans = 1;
for(; b; b >>= 1, a = 1ll * a * a % mod)
if(b & 1) ans = 1ll * ans * a % mod;
return ans;
}
inline void Gauss(Matrix &a){
int tmp, f;
for(int i = 1; i <= n; i++){
int r = i;
for(int j = i + 1; j <= n; j++) if(a[j][i] > a[i][i]) r = j;
for(int j = i; j <= n + 1; j++) swap(a[i][j], a[r][j]);
if(!a[i][i]) continue;
tmp = Pow(a[i][i], mod - 2);
for(int j = i + 1; j <= n; j++){
f = 1ll * a[j][i] * tmp % mod;
for(int k = i; k <= n + 1; k++)
del(a[j][k], 1ll * -a[i][k] * f % mod);
}
}
for(int i = n; i >= 1; i--){
for(int j = i + 1; j <= n; j++)
del(a[i][n+1], 1ll * -a[j][n+1] * a[i][j] % mod);
a[i][n+1] = 1ll * a[i][n+1] * Pow(a[i][i], mod - 2) % mod;
}
}
int main(){
read(n), read(e);
for(int i = 1, x, y; i <= e; i++)
read(x), read(y), g[x].push_back(y), g[y].push_back(x);
for(int i = 1; i <= n; i++) Ans[(1<<n)-1][i] = 0;
for(int s = (1 << n) - 2; s; s--){
for(int i = 1; i <= n + 1; i++)
for(int j = 1; j <= n + 1; j++) a[i][j] = 0;
for(int i = 1; i <= n; i++) if((1 << i - 1) & s){
int tmp = Pow(g[i].size(), mod - 2);
for(int j = 0; j < g[i].size(); j++){
int v = g[i][j];
if(!((1 << v - 1) & s)) up(a[i][n+1], Ans[s|(1<<v-1)][v]);
else a[i][v] = (-tmp + mod) % mod;
}
a[i][n+1] = 1ll * tmp * a[i][n+1] % mod;
a[i][n+1] = (a[i][n+1] + 1) % mod, a[i][i] = 1;
}
Gauss(a);
for(int i = 1; i <= n; i++) Ans[s][i] = a[i][n+1];
}
int q; read(q);
while(q--){
int num = 0, s, all = (1 << n) - 1; read(num);
for(int i = 1, x; i <= num; i++) read(x), all ^= (1 << x - 1);
read(s), all |= (1 << s - 1), printf("%d\n", Ans[all][s]);
}
return 0;
}