【YBTOJ】【Luogu P3232】[HNOI2013]遊走
阿新 • • 發佈:2021-01-08
連結:
題目描述:
給定一個 \(n\) 個點 \(m\) 條邊的無向連通圖,頂點從 \(1\) 編號到 \(n\),邊從 \(1\) 編號到 \(m\)。
小 Z 在該圖上進行隨機遊走,初始時小 Z 在 \(1\) 號頂點,每一步小 Z 以相等的概率隨機選擇當前頂點的某條邊,沿著這條邊走到下一個頂點,獲得等於這條邊的編號的分數。當小 Z 到達 \(n\) 號頂點時遊走結束,總分為所有獲得的分數之和。 現在,請你對這 \(m\) 條邊進行編號,使得小 Z 獲得的總分的期望值最小。
\(2\leq n \leq 500,1 \leq m \leq 125000\)。
正文:
邊的數量太大,所以我們不能以直接求邊的期望經過次數。那我們就求每個點的。設 \(f_i\)
其中 \(deg_i\) 表示第 \(i\) 個點的度數。
現在我們的問題是如何求 \(f_i\) 了。
容易得到:
\[f_i=\sum_{(i,j)\in E,j\not=n}\frac{f_j}{deg_u}+(i==1) \]如果我們直接 DFS,肯定是過不了的。可以用高斯消元。
首先 \(1\) 號點到 \(n\) 點的概率肯定是 \(1\),其次是點 \(i\) 的期望次數減去其它點轉移過來的期望次數肯定是 \(0\)
因為到 \(n\) 停止遊走,不能考慮 \(n\),這樣就能構成 \(n-1\) 個線性方程組,可以開搞高消了。
最後將 \(g_i\) 倒序排序,就能得到答案了。
程式碼:
int n, m; int deg[N]; double f[N], a[N][N], g[M], ans; struct edge { int from, to, nxt; }e[M << 1]; int head[N], tot; void add (int u, int v) { e[++tot] = (edge){u, v, head[u]}, head[u] = tot; } void Gauss(int n) { for (int i = 1; i <= n; i++) { int mxi = i; for (int j = i + 1; j <= n; j++) if(fabs(a[mxi][i]) < fabs(a[j][i])) mxi = j; swap(a[mxi], a[i]); double inv = a[i][i]; for (int j = i; j <= n + 1; j++) a[i][j] /= inv; for (int j = i + 1; j <= n; j++) { inv = a[j][i]; for (int k = i; k <= n + 1; k++) a[j][k] -= a[i][k] * inv; } } f[n] = a[n][n + 1]; for (int i = n - 1; i; --i) { for (int j = i + 1; j <= n; ++j) a[i][n + 1] -= f[j] * a[i][j]; f[i] = a[i][n + 1] / a[i][i]; } } int main() { scanf ("%d%d", &n, &m); for (int i = 1; i <= m; i++) { int x, y; scanf ("%d%d", &x, &y); add (x, y), add(y, x); deg[x]++, deg[y]++; } for (int i = 1; i < n; i++) { a[i][i] = 1.0; for (int j = head[i]; j; j = e[j].nxt) { int v = e[j].to; if (v != n) a[i][v] -= 1.0 / deg[v]; } } a[1][n] = 1; Gauss (n - 1); for (int i = 1; i <= m; i++) g[i] = f[e[i<<1].from] / deg[e[i<<1].from] + f[e[i<<1].to] / deg[e[i<<1].to]; sort (g + 1, g + 1 + m); for (int i = 1; i <= m; i++) ans += g[i] * (m - i + 1.0); printf ("%.3lf", ans); return 0; }