bzoj3143 [Hnoi2013]游走 (期望概率DP + 高斯消元)

本文介绍了解决BZOJ3143问题的一种方法,通过使用高斯消元法来确定边的编号顺序,使得随机游走在图上的总分期望值达到最小。文章详细阐述了如何建立数学模型,并给出了具体的实现代码。

bzoj3143 [Hnoi2013]游走

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3143

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

数据范围
2≤N≤500且是一个无向简单连通图。

题解:
这里提供了一种用高斯消元解方程来解决有后效性问题的思路。

我们要得到每条边的期望经过次数,然后把期望最大的排名尽量小
把求没条边的期望经过次数转化为每个点的期望经过次数,
则 令 l[i]为一条边的期望经过次数,f[u]为一个点的期望经过次数,
那么: l[i]=f[u]* (1/degree(u))+f[v]*(1/degree(v))
特殊地,对于一个端点为n的点,要忽略点n,因为不可能从点n过来

DP方程应该很容易得到:
f[u]=∑( f[v]*(1/degree(v))) (v与u有边)
特殊地
f[1]=∑( f[v]*(1/degree(v))) +1 (1本来经过了一次)
对于与n有边的点 f[u]=∑( f[v]*(1/degree(v))) (v!=n, u与点n有边)
f[n]用不到。
发现有后效性,这个DP不了诶。
但是我们呢得到了一个 关于 f[i]的,(n-1)元一次方程组。
于是 高斯消元解方程,得到f[i]求l[i],得到答案。
发现自己的高消快忘了……

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m;
const int N=505;
const double EPS=1e-10;
int head[N],to[2*N*N],nxt[2*N*N],du[N],num=0;
int uu[N*N],vv[N*N];
double a[N][N],f[N],l[2*N*N];
void build(int u,int v)
{
    num++;
    to[num]=v;
    nxt[num]=head[u];
    head[u]=num;
}
void gauss()
{
    int cnt=0; 
    for(int i=1;i<=n;i++) 
    {
        int j=-1; double mx=EPS;
        for(int k=cnt+1;k<=n;k++)
        {
            if(fabs(a[k][cnt+1])>mx)
            {
                mx=fabs(a[k][cnt+1]); j=k;
            }
        }
        if(j==-1) continue;
        for(int k=1;k<=n+1;k++)
        swap(a[cnt+1][k],a[j][k]);
        for(int j=1;j<=n;j++)
        {
            if(j==cnt+1) continue;
            if(fabs(a[j][i])<EPS) continue;
            double r=a[j][i]/a[cnt+1][i];
            for(int k=1;k<=n+1;k++)
            {
                a[j][k]-=a[cnt+1][k]*r;
            }
        }
        cnt++;  
    }
    for(int i=1;i<=n;i++)
    {
        f[i]=a[i][n+1]/a[i][i];
    }   
}
bool cmp(const double &A,const double &B)
{
    return A-B>EPS;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n+1;i++)
    for(int j=0;j<=n+1;j++) 
    a[i][j]=0.0;
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        uu[i]=u; vv[i]=v;
        build(u,v); build(v,u);
        du[u]++; du[v]++; 
    }
    n--;
    for(int i=1;i<=n;i++)
    {
        a[i][i]=-1.0;
        if(i==1) a[i][n+1]=-1.0;
        for(int j=head[i];j;j=nxt[j])
        {           
            int v=to[j];
            if(v==n+1) continue;
            a[i][v]=(double)1.0/du[v];      
        }
    }

    gauss();
    n++;
    for(int i=1;i<=m;i++)
    {
        l[i]=(double)f[uu[i]]/du[uu[i]]+(double)f[vv[i]]/du[vv[i]];
    }

    sort(l+1,l+m+1,cmp);
    double ans=0;
    for(int i=1;i<=m;i++)
    ans+=(double)l[i]*i;
    printf("%0.3lf\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值