[BZOJ]3143 游走 期望 + 高斯消元

3143: [Hnoi2013]游走

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 3110   Solved: 1365
[ Submit][ Status][ Discuss]

Description

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

Input

第一行是正整数N和M,分别表示该图的顶点数 和边数,接下来M行每行是整数u,v(1≤u,v≤N),表示顶点u与顶点v之间存在一条边。 输入保证30%的数据满足N≤10,100%的数据满足2≤N≤500且是一个无向简单连通图。

Output

仅包含一个实数,表示最小的期望值,保留3位小数。

Sample Input

3 3
2 3
1 2
1 3

Sample Output

3.333

HINT

(1,2) 编号为 1 ,边 (1,3) 编号 2 ,边 (2,3) 编号为 3

Source

[ Submit][ Status][ Discuss]


HOME Back

这道题看起来好像是简单的逆拓扑序求期望, 事实上并不行, 因为他是无向图. 

我们先想一下题目中所要求的东西, 可以想到一个简单的贪心思路, 那就是算出每条边的期望经过次数, 然后排序, 把期望经过次数少的分大编号, 这样肯定是最优的.

那么问题就转化成了求边的期望经过次数. 边的期望次数可以用点的期望次数来算. 可以发现是一个n行n列的方程, 直接高斯消元即可.

#include<stdio.h>
#include<cmath>
#include<algorithm>
using namespace std;
const double eps = 1e-10;
const int maxn = 505;
const int maxm = 250005;
int st[maxm], ed[maxm], d[maxn], h[maxn], cnt, num, n, m;
double a[maxn][maxn], f[maxm], w[maxm], ans;
struct edge{int nxt, v;}e[maxm * 2];
inline const int read(){  
    register int f = 1, x = 0;  
    register char ch = getchar();  
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}  
    while(ch >= '0' && ch <= '9') x = (x<<3) + (x<<1) + ch - '0', ch = getchar();  
    return f * x;  
}  
inline void add(int u, int v){
    e[++num].v = v, e[num].nxt = h[u], h[u] = num;
    e[++num].v = u, e[num].nxt = h[v], h[v] = num;
}
inline void Gauss(){
    for(int i = 1; i <= n; ++i){
        int j = -1;
        for(int k = cnt + 1; k <= n; ++k)
            if(fabs(a[k][i]) > fabs(a[j][i])) j = k;
            if(j == -1) continue;
             for(int k = i; k <= n + 1; ++k) swap(a[j][k], a[cnt + 1][k]);
             for(j = 1; j <= n; ++j){
                if(fabs(a[j][i]) < eps || j == cnt + 1) continue;
                double r = a[j][i] / a[cnt + 1][i];
                for(int k = i; k <= n + 1; ++k)
                    a[j][k] -= r * a[cnt + 1][k];
             }
             ++cnt;
    }
    for(int i = 1; i <= n; ++i) f[i] = a[i][n + 1] / a[i][i];
}
int main(){
    n = read(), m = read(), --n;
    for(int i = 1; i <= m; ++i){
        st[i] = read(), ed[i] = read();
        add(st[i], ed[i]), d[st[i]]++,d[ed[i]]++;
    }
    for(int u = 1; u <= n; ++u){
        for(int i = h[u]; i; i = e[i].nxt)
            if(e[i].v != n + 1) a[u][e[i].v] += (double) 1.0 / d[e[i].v];
        a[u][u] = -1;
    }
    a[1][n+1]=-1;
    Gauss();
    for(int i = 1; i <= m; ++i) w[i] = (double) f[st[i]] / d[st[i]] + (double) f[ed[i]] / d[ed[i]];
    sort(w + 1, w + m + 1);
    for(int i = 1; i <= m; ++i) ans += (m - i + 1) * w[i];
    printf("%0.3lf\n", ans);
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值