图上游走 / 游走
题目链接:ybt金牌导航1-1-6 / ybt金牌导航8-2-2 / luogu P3232
题目大意
对于一个无量连通图,一个人要从 1 点走到最后一个点,每次在一个点会等概率地走到与这个点相连的点,费用是这条边的编号。
现在你要给边编号,使得总分期望最小。
思路
这道题,我们看到边的数量很大,就不能直接求边的。
那我们就从点入手。
设
f
i
f_i
fi 为第
i
i
i 个点的期望经过次数,那我们可以根据这个求出某条边
e
e
e 的期望经过次数
=
f
f
r
o
m
d
u
f
r
o
m
+
f
t
o
d
u
t
o
=\dfrac{f_{from}}{du_{from}}+\dfrac{f_{to}}{du_{to}}
=dufromffrom+dutofto(
d
u
i
du_i
dui 就是这个点的度数)。
那你知道这个后,我们就可以贪心,让期望经过次数多的边赋的权值小。
现在来看怎么求
f
i
f_i
fi。
那我们可以得到它等于它可以到的点的
f
f
f 值乘走过来它这里的概率。
但是有一些特殊的地方,就比如起点
1
1
1 点,因为从它出发走到终点的概率一定是
1
1
1,那我们要让
f
1
f_1
f1 在原来的基础上加一。其它的点出去从别的点走过来的其它概率是
0
0
0,所以不用再加什么。
但是
n
n
n 是考虑不了的,因为走到
n
n
n 就停止了。
那一共就构成了 n − 1 n-1 n−1 个方程组,我们可以用高斯消元得出解。
然后就按着上面说的做,就可以了。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int from, to, nxt;
}e[500001];
int n, m, x, y, le[501], KK, sz, du[501], maxn;
double f[501][501], line[500001], ans;
void add(int x, int y) {
e[++KK] = (node){x, y, le[x]}; le[x] = KK;
e[++KK] = (node){y, x, le[y]}; le[y] = KK;
du[x]++;
du[y]++;
}
double abss(double now) {
if (now < 0) return -now;
return now;
}
void gas(int n) {//高斯消元
for (int i = 1; i <= n; i++) {
maxn = i;
for (int j = i + 1; j <= n; j++)
if (abss(f[j][i]) > abss(f[maxn][i]))
maxn = j;
for (int j = 1; j <= n + 1; j++)
swap(f[i][j], f[maxn][j]);
for (int j = 1; j <= n; j++)
if (i != j) {
for (int k = i + 1; k <= n + 1; k++)
f[j][k] = f[j][k] - (f[i][k] * (f[j][i] / f[i][i]));
}
}
for (int i = 1; i <= n; i++)
f[i][n + 1] /= f[i][i];
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
for (int i = 1; i < n; i++) {
f[i][i] = -1.0;//把右边的等于f[i]移过来,就是变成负的了
for (int j = le[i]; j; j = e[j].nxt) {
if (e[j].to == n) continue;
f[i][e[j].to] = 1.0 / (1.0 * du[e[j].to]);//这条边连着的点有多少的几率会走到这个点
}
}
f[1][n] = -1.0;//1点到终点的概率一定是1
gas(n - 1);
for (int i = 1; i <= m; i ++)//得出经过边的次数
line[i] = f[e[i << 1].from][n] / (1.0 * du[e[i << 1].from]) + f[e[i << 1].to][n] / (1.0 * du[e[i << 1].to]);
sort(line + 1, line + m + 1);//贪心,让经过次数最多的边的权值尽可能小
for (int i = 1; i <= m; i++)//算出总分
ans += line[i] * (1.0 * (m - i + 1.0));
printf("%.3lf", ans);
return 0;
}