[题解] BZOJ 3143 [HNOI2013] 游走

题目描述 Description
一个无向连通图,顶点从 1 1 编号到N,边从 1 1 编号到M。小Z在该图上进行随机游走,初始时小Z在 1 1 号顶点,每一步小Z以相等的概率随机选择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z到达N号顶点时游走结束,总分为所有获得的分数之和。
现在,请你对这 M M 条边进行编号,使得小Z获得的总分的期望值最小。
输入描述 Input Description
输入文件第一行是正整数N M M ,分别表示该图的顶点数
和边数,接下来M行每行是整数u,v,表示顶点 u u 与顶点v之间存在一条边。
输出描述 Output Description
仅包含一个实数,表示最小的期望值,保留3位小数。
样例输入 Sample Input
3 3
2 3
1 2
1 3
样例输出 Sample Output
3.333
样例解释
边(1,2)编号为1,边(1,3)编号2,边(2,3)编号为3
数据范围及提示 Data Size & Hint
30%的数据 N10 N ≤ 10
100%的数据 2N500 2 ≤ N ≤ 500 且是一个无向简单连通图。

Solution

很容易就能看出这是一道期望题
我们要知道每条边被经过的期望值,然后期望值小的给一个比较大的编号
怎么求每条边被经过的期望值?
每条边是否被经过只由它所连接的两端节点决定
由于每个点到与之相连的任意一条边的期望相等,所以到某条特定的边的期望就是经过这个点的期望值乘上 1/ 1 / 该点的总边数
那么一条边被经过的期望就应该是两端点到这条边的期望值之和
那么我们就将求边的期望转化为求点的期望

显然的,可以看出每个点的期望是由与其相连的点的期望决定的
f[x] f [ x ] 为点 x x 的期望,num[x]为点 x x 的总边数,点x1,x2,x3,...,xk与其相邻,则 f[x]=ki=1f[i]num[i] f [ x ] = ∑ i = 1 k f [ i ] n u m [ i ]
每个点都能够列出这样一个方程

直接用高斯消元法解即可
注意
1 1 与点n有些特殊

由于”游走”是从点 1 1 开始,则计算点1期望时实际期望应该是原期望+1
若有点和 n n 相连,那么在计算期望时是不需要将其算入的,因为到了点n的时候是不会继续”游走”了

注意着两个点,就没有什么问题了

记得将每条边的两端存起来这样最后计算每条边的期望就很简单了

代码如下

#include <bits/stdc++.h>
using namespace std;
#define N 510
#define eps 1e-8
int to[N*N*2],next[N*N*2],head[N],tot;
int F[N*N],T[N*N];
double q[N*N];
int num[N];
double f[N][N];
double im[N*N*2];
int n,m;
int read() {
    int ans=0,flag=1;
    char ch=getchar();
    while((ch>'9' || ch<'0') && ch!='-') ch=getchar();
    if(ch=='-') flag=-1,ch=getchar();
    while(ch>='0' && ch<='9') ans=ans*10+ch-'0',ch=getchar();
    return ans*flag;
}
void addedge(int u,int v) {
    num[u]++;
    to[++tot]=v;
    next[tot]=head[u];
    head[u]=tot;
}
int main() {
    n=read(),m=read();
    for(int i=1;i<=m;i++) {
        int u=read(),v=read();
        addedge(u,v);
        addedge(v,u);
        F[i]=u;T[i]=v;
    }
    f[1][n]=1;
    for(int i=1;i<n;i++) {
        f[i][i]=1;
        for(int j=head[i];j;j=next[j])
            if(to[j]!=n)
                f[i][to[j]]=-1.0/num[to[j]];
    }
    for(int i=1;i<n;i++) {
        int now=i;
        double s=f[i][i];
        for(int j=i+1;j<n;j++)
            if(fabs(s-1.0)-fabs(f[j][i]-1.0)>=eps)
                {now=j;s=f[j][i];}
        if(now!=i) {
            for(int j=1;j<=n;j++)
                swap(f[i][j],f[now][j]);
        }
        for(int j=n;j>=i;j--) f[i][j]/=f[i][i];
        for(int j=1;j<n;j++)
            if(i!=j)
                for(int k=n;k>=i;k--)
                    f[j][k]-=f[j][i]*f[i][k];
    }
    for(int i=1;i<=m;i++) {
        if(F[i]!=n)
            q[i]+=f[F[i]][n]*(1.0/num[F[i]]);
        if(T[i]!=n)
            q[i]+=f[T[i]][n]*(1.0/num[T[i]]);
    }
    sort(q+1,q+m+1);
    double ans=0;
    for(int i=1;i<=m;i++)
        ans+=q[i]*((m-i+1)*1.0);
    printf("%.3lf\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值