P3232 [HNOI2013]游走

P3232 [HNOI2013]游走

洛谷传送门

Solution

看到题不要慌,不要因为是期望而慌张。像我一样

我们一步一步分析(●’◡’●)

首先,根据贪心的思想,期望经过次数多的边我们给它更小的编号。

那么现在就想怎么求出每条边的期望经过次数

经过一番思考,发现每条边只与它的两个点及点的度数有关,用式子写一下就是:
g e = f u d u + f v d v g_e=\frac {f_u}{d_u}+\frac {f_v}{d_v} ge=dufu+dvfv
(其中 e e e ⟨ u , v ⟩ \langle u,v\rangle u,v 这条边, g e g_e ge 表示 e e e 的期望经过次数, f u f_u fu 表示 u u u 这个点的期望经过次数, d u d_u du 表示 u u u 的度数, f v , d v f_v,d_v fv,dv 同理。)

因为每次经过 u u u 的时候,都有 1 d u \dfrac 1{d_u} du1 的概率经过这条边, v v v 同理。

那那么我们就把问题转化到怎么求出每个点的期望经过次数

又经过一波观察,发现每个点只和它相连的点及点的度数有关,用式子写出来就是:
f u = ∑ v ∈ E u f v d v f_u=\sum_{v\in E_u}\frac {f_v}{d_v } fu=vEudvfv
(其中 E u E_u Eu 表示和 u u u 相连的点集。)

这个式子的道理和上面和边有关的那个式子是一样的^o^/

但是有两个特殊的点:

1.第 1 1 1 个节点是开始的节点,也就是刚开始就走过了,所以 f 1 f_1 f1 的转移不一样: f 1 = 1 + ∑ f v d v f_1=1+\sum \frac {f_v}{d_v} f1=1+dvfv

2.第 n n n 个节点是结束的节点,也就是走到 n n n 即结束,所以任何一个点不能从 n n n 转移,计算 g g g 的时候也不能将 f n d n \dfrac {f_n}{d_n} dnfn 计入答案。最简单的方法就是 f n = 0 f_n=0 fn=0


接下来我们可以将 f f f n − 1 n-1 n1 个式子看做 n − 1 n-1 n1 n − 1 n-1 n1 元方程组,用高斯消元求解即可。

注意:因为是方程组,式子的形式都是 a k , 1 ∗ f 1 + a k , 2 ∗ f 2 + ⋯ + a k , i ∗ f i + ⋯ + a k , n ∗ f n = f k a_{k,1}*f_1+a_{k,2}*f_2+\cdots+a_{k,i}*f_i+\cdots +a_{k,n}*f_n=f_k ak,1f1+ak,2f2++ak,ifi++ak,nfn=fk a k , i a_{k,i} ak,i 代表 f i f_i fi 在第 k k k 个式子中的系数),所以对于特殊的 f 1 f_1 f1 ,式子要变成 ∑ a 1 , i ∗ f i = f 1 − 1 \sum a_{1,i}*f_i=f_1-1 a1,ifi=f11

Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;
const int N=510,M=125010;
const double eps=1e-9;
int n,m,cnt,head[N],d[N],u[M],v[M];
struct edge{
    int to,nxt;
}e[M<<1];
double a[N][N],g[M],ans;

inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    return x*f;
}

inline void add(int u,int v){
    e[++cnt].to=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;

    e[++cnt].to=u;
    e[cnt].nxt=head[v];
    head[v]=cnt;

    d[u]++; d[v]++;
}

inline void gauss(){
    for(int i=1;i<=n;i++){
        int max=i;
        for(int j=i+1;j<=n;j++) if(fabs(a[j][i])>fabs(a[max][i])) swap(max,j);
        if(max!=i)
            for(int j=1;j<=n+1;j++) swap(a[i][j],a[max][j]);
        if(fabs(a[i][i])<eps) continue;
        for(int j=1;j<=n;j++){
            if(j!=i){
                double tmp=a[j][i]/a[i][i];
                for(int k=i+1;k<=n+1;k++)
                    a[j][k]-=a[i][k]*tmp;
            }
        }
    }
    for(int i=1;i<=n;i++)
        a[i][n+1]/=a[i][i];
}

int main(){
    n=read(); m=read();
    for(int i=1;i<=m;i++){
        u[i]=read(); v[i]=read();
        add(u[i],v[i]);
    }
    n--; a[1][n+1]=-1.0;
    for(int u=1;u<=n;u++){
        a[u][u]=-1;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(v!=n+1) a[u][v]=1.0/d[v];
        }
    }
    gauss();
    for(int i=1;i<=m;i++)
        g[i]=a[u[i]][n+1]/d[u[i]]+a[v[i]][n+1]/d[v[i]];
    sort(g+1,g+m+1);
    for(int i=1;i<=m;i++)
        ans+=g[i]*(m-i+1);
    printf("%.3lf\n",ans);
    return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值