【ybt金牌导航1-1-6】【luogu P3232】图上游走 / 游走

图上游走 / 游走

题目链接:ybt金牌导航1-1-6 / 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 n1 个方程组,我们可以用高斯消元得出解。

然后就按着上面说的做,就可以了。

代码

#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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43346722/article/details/112422494