【BZOJ3143】[HNOI2013]游走

题目链接

题意

给一张无向连通图,要求给m条边中的每条边一个1~m的权值使得从1号点到n号点的期望最小
选择边的概率相等
走到n就停止

n500 n ≤ 500

Sol

DP? 这是个无向连通图,似乎不好DP

做这种题先把题目要求的式子给写出来:

L1(ui,vi)val(ui,vi)L1ui,vidu[ui] ∑ 每 一 条 路 径 ∑ ( u i , v i ) L − 1 v a l ( u i , v i ) ∏ u i , v i L − 1 d u [ u i ]

好像无从下手,因为枚举路径是不可做的
显然的我们可以考虑每一条边对答案的贡献,就是:

i=1m i val(i)L1ui,vidu[ui] ∑ i = 1 m ∑ 每 一 条 包 含 边   i   路 径 v a l ( i ) ∏ u i , v i L − 1 d u [ u i ]

这样还是不好处理,但已经可以看出贪心策略了,不妨先假设每一条边的权值都为1,然后算出下面那一坨东西排序,再依次赋权值即可

注意到此时边的贡献可以转化为边两头点贡献的和,就是:

E(i)=P(ui)du[ui]+P(vi)du[vi] E ( i ) = P ( u i ) d u [ u i ] + P ( v i ) d u [ v i ]

E(i) E ( i ) 表示边i的贡献(边权是1), P(u) P ( u ) 表示走到了 u u <script type="math/tex" id="MathJax-Element-822">u</script>点的概率

由于有环不能递推,但是可以列方程,然后高斯消元
列方程比较简单,只是有几个细节罢了,自己YY吧

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
typedef long long ll;
typedef double db;
const int N=501;
db du[N];
struct edge{
    int to,next;
}A[(N*N)<<1];
int n,m;
int head[N];int cnt=0;
inline void add(int x,int y){A[++cnt]=(edge){y,head[x]};head[x]=cnt;}
db a[N][N];
db P[N];
const db eps=1e-12;
struct ED{
    int u,v;db val;
    inline bool operator <(ED b)const{
        return val>b.val;
    }
}B[N*N];
inline void Gauss_Jordan()
{
    for(register int h=1;h<=n;++h){
        register int p=h;
        for(register int i=h+1;i<=n;++i){
            if(fabs(a[i][h])<=eps) continue;
            if(fabs(a[i][h])>fabs(a[p][h])) p=i;
        }
        if(p!=h) swap(a[p],a[h]);
        register db t=a[h][h];
        for(register int i=h;i<=n+1;++i) a[h][i]/=t;
        for(register int i=1;i<=n;++i){
            if(i==h||fabs(a[i][h])<=eps) continue;
            t=a[i][h];
            for(register int j=h;j<=n+1;++j)  a[i][j]=(a[i][j]-a[h][j]*t);
        }
    }
    for(register int i=1;i<=n;++i) P[i]=a[i][n+1];
    return;
}
int main()
{
    register int u,v;
    n=read();m=read();
    for(register int i=1;i<=m;++i){
        u=read();v=read();
        ++du[u];++du[v];B[i].u=u;B[i].v=v;
        add(u,v);add(v,u);
    }
    for(register int u=1;u<=n;++u){
        a[u][u]=1.00;a[u][n+1]=0.0;
        for(register int v,i=head[u];i;i=A[i].next){
            v=A[i].to;
            if(v!=n) a[u][v]+=(-1.00/du[v]);
        }
    }
    a[1][n+1]=1.00;
    Gauss_Jordan();
    for(register int i=1;i<=m;++i){
        if(B[i].u!=n) B[i].val+=P[B[i].u]/du[B[i].u];
        if(B[i].v!=n) B[i].val+=P[B[i].v]/du[B[i].v];
    }
    sort(B+1,B+1+m);
    db head=0;register db ans=0;
    for(register int i=1;i<=m;++i){
        ++head;ans+=head*B[i].val;
    }
    printf("%.3lf\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值