啊 我永远喜欢期望题
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;
}