BZOJ 3143 游走 | 数学期望 高斯消元

啊 我永远喜欢期望题

BZOJ 3143 游走

题意

有一个n个点m条边的无向联通图,每条边按1m编号,从1号点出发,每次随机选择与当前点相连的一条边,走到这条边的另一个端点,一旦走到n号节点就停下。每经过一条边,要付出这条边的编号这么多的代价。现将所有边用1m重新编号,使总代价的期望最小,求这个最小值。

题解

我们可以求出每条边的期望经过次数,然后贪心地让经过次数多的边编号小即可。

直接用边来列方程求经过次数似乎列不出来,我们借助点来列方程。

设x[u]为从某个点出发的次数的期望,v为与u相连的点,d[v]为点d的度,则:

\[x[u] = \sum \frac{x[v]}{d[v]} \]

特殊地,不能从点n出发,所以x[n] = 0;第一次从点1出发,\(x[u] = 1 + \sum \frac{x[v]}{d[v]}\)

解出所有x后,设一条边的两个端点是u和v,则经过每条边的次数的期望是:

\[\frac{x[u]}{d[u]} + \frac{x[v]}{d[v]} \]

代码如下:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
	if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
	x = x * 10 + c - '0';
    if(op) x = -x;
}
template <class T>
void write(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar('0' + x % 10);
}

const int N = 505, M = 250005;
int n, m, u[M], v[M], d[N];
double ans[M], x[N], f[N][N], res;
void build(){
    for(int i = 1; i <= n; i++)
	f[i][i] = -1;
    for(int e = 1; e <= m; e++){
	f[u[e]][v[e]] += 1.0 / d[v[e]];
	f[v[e]][u[e]] += 1.0 / d[u[e]];
    }
    for(int i = 1; i <= n; i++)
	f[n][i] = 0;
    f[n][n] = 1, f[n][n + 1] = 0;
    f[1][n + 1] = -1;
}
void Gauss(){
    for(int i = 1; i <= n; i++){
	int l = i;
	for(int j = i + 1; j <= n; j++)
	    if(fabs(f[j][i]) > fabs(f[l][i])) l = j;
	if(l != i)
	    for(int j = i; j <= n + 1; j++)
		swap(f[i][j], f[l][j]);
	for(int j = n + 1; j >= i; j--)
	    f[i][j] /= f[i][i];
	for(int j = i + 1; j <= n; j++)
	    for(int k = n + 1; k >= i; k--)
		f[j][k] -= f[j][i] * f[i][k];
    }
    for(int i = n; i; i--){
	x[i] = f[i][n + 1];
	for(int j = 1; j < i; j++)
	    f[j][n + 1] -= f[j][i] * x[i];
    }
}
int main(){
    read(n), read(m);
    for(int i = 1; i <= m; i++)
	read(u[i]), read(v[i]), d[u[i]]++, d[v[i]]++;
    build();
    Gauss();
    for(int i = 1; i <= m; i++)
	ans[i] = x[u[i]] / d[u[i]] + x[v[i]] / d[v[i]];
    sort(ans + 1, ans + m + 1);
    for(int i = 1; i <= m; i++)
	res += ans[i] * (m - i + 1);
    printf("%.3lf\n", res);
    return 0;
}
posted @ 2017-12-07 11:48  胡小兔  阅读(427)  评论(0编辑  收藏  举报