【题目描述】

一个无向连通图,顶点从 1 编号到 N ,边从 1 编号到 M 。

小Z 在该图上进行随机游走,初始时 小Z 在 1 号顶点,每一步 小Z 以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当 小Z 到达 N 号顶点时游走结束,总分为所有获得的分数之和。 现在,请你对这 M 条边进行编号,使得 小Z 获得的总分的期望值最小。

【题目链接】

BZOJ 3143 游走 【HNOI 2013】

【解题思路】

由于标号方案与边的期望贡献无关,所以可以先计算边的期望贡献,然后贪心地标号,即对最大的边标号 $1$,次大的边标号 $2$ …… 最小的边标号 $m$。

一条边的期望贡献为经过该边的期望次数乘上边权,所以下面的任务是求经过某边的期望次数。

类似 [BZOJ 3036] 绿豆蛙的归宿 - 期望DP,我们可以由点的期望来计算边的期望。

设 $e_i$ 为经过点 $i$ 的期望次数,$d_i$ 为点 $i$ 的度,那么对于一条边 $(u, v), v \not= n$,经过其的期望次数为 $e_u \times \frac 1 d_u + e_v \times \frac 1 d_v$,即考虑从 $u$ 或 $v$ 经过这条边。

特别的,对于 $(u, n)$ 的边,经过其的期望次数为 $e_u \times \frac 1 d_u$,这是因为我们走到 $n$ 就结束了。

下面考虑怎么求 $e_i$,因为图不是 DAG,所以无法递推,只能通过高斯消元解方程组来求。

我们试着列出方程组:

($p_{x, y}$ 表示从 $x$ 走到 $y$ 的概率)

对于起点 1 : $$ e_1 = 1 + \sum_{1 \lt j \lt n}e_j \times p_{j, i} $$

加 1 是因为一开始就经过了一次。

对于其他点 $i$ : $$ e_i = \sum_{1 \leq j \lt n,j \not= i}e_j \times p_{j, i} $$

为了解方程,我们把未知数移到一边,化为一般形式:

对于起点 1 : $$ -e_1 + \sum_{1 \lt j \lt n}e_j \times p_{j, i} = -1 $$

对于其他点 $i$ : $$ -e_i + \sum_{1 \leq j \lt n,j \not= i}e_j \times p_{j, i} = 0 $$

高斯消元把这个方程组解出来即可。

至于求 $p_{x, y}$,如果 $x, y$ 有边,那么 $p_{x, y} = \frac 1 d_x$,否则 $p_{x, y} = 0$。

总结:图上求期望的一般思路,将边的期望转化为点的期望,使用递推(DAG)或高斯消元(一般图)求解。

【AC代码】

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cassert>
 
const int MAXN = 500;
const int MAXM = MAXN * (MAXN - 1) / 2;
 
bool G[MAXN][MAXN];
int deg[MAXN];
int n, m;
 
inline void link(int u, int v){
    G[u][v] = G[v][u] = true;
    deg[u]++, deg[v]++;
}
 
double a[MAXN][MAXN + 1 + 1];
 
inline void buildEquations(){
    for(int i = 0; i < n; i++) a[i][i] = -1;
    a[0][n] = -1;
    for(int i = 0; i < n - 1; i++) for(int j = 0; j < n; j++) if(G[i][j]){
        a[j][i] += 1.0 / deg[i];
    }
}
 
inline void guassJordan(){
    for(int i = 0; i < n; i++){
        int p = i;
        for(int j = i + 1; j < n; j++) if(std::fabs(a[j][i]) > std::fabs(a[p][i])) p = j;
        if(p != i) for(int j = i; j <= n; j++) std::swap(a[i][j], a[p][j]);
 
        assert(std::fabs(a[i][i]) >= 1e-10);
 
        for(int j = 0; j < n; j++) if(i != j){
            for(int k = n; k >= i; k--){
                a[j][k] -= a[i][k] / a[i][i] * a[j][i];
            }
        }
    }
}
 
double exNode[MAXN], exEdge[MAXM];
 
inline double solve(){
    buildEquations(), guassJordan();
    for(int i = 0; i < n; i++) exNode[i] = a[i][n] / a[i][i];
 
    int p = 0;
    for(int i = 0; i < n; i++) for(int j = i + 1; j < n; j++) if(G[i][j]){
        exEdge[p] += exNode[i] / deg[i];
        if(j != n - 1) exEdge[p] += exNode[j] / deg[j];
 
        p++;
    }
 
    std::sort(exEdge, exEdge + m);
 
    double ans = 0;
    for(int i = 0; i < m; i++) ans += exEdge[i] * (m - i);
    return ans;
}
 
int main(){
    std::cin >> n >> m;
    for(int i = 0; i < m; i++){
        int u, v;
        std::cin >> u >> v, u--, v--;
 
        link(u, v);
    }
 
    std::cout << std::fixed << std::setprecision(3) << solve() << std::endl;
 
    return 0;
}


就是这样啦~