bzoj3143 [Hnoi2013]游走(概率+gauss)

题目链接

题外话:
小Z,你的袜子找到了吗?

分析:
题目要求使得小Z获得的总分的期望值最小
那么考虑我们知道那些算法可以达到这个目的:
二分(不可能),线性规划(有门,但是不等式呢),dp(有点靠谱)

但是我们的目的是确定边的编号,dp之类的应该是没有办法的

期望=概率*权值

想要期望小,实际上就是 “概率*权值” 尽量小
显然,我们希望概率越小的边权值越大(mmp又是一个贪心。。。)

我们可以对于每一条边计算经过ta的概率:
f(w(x,y))=(x=n)?0:f(w(u,x))out[w(u,x)]+(y=n)?0:f(w(y,v))out[w(y,v)] f ( w ( x , y ) ) = ( x = n ) ? 0 : f ( w ( u , x ) ) ∗ o u t [ w ( u , x ) ] + ( y = n ) ? 0 : f ( w ( y , v ) ) ∗ o u t [ w ( y , v ) ]
out[w] o u t [ w ] 表示从边w出来的概率

注意:x,y不能是n,因为到达n后游走就结束了,必然不会再出来了

直接求解边的概率列方程比较复杂
所以我们换一个思路,先解出到达每个点的概率,那么边的概率就可以算出来了

对于每一个点(除了n号点,到达n后游走就结束了):
g(x)=g(y)deg(y) g ( x ) = ∑ g ( y ) d e g ( y ) (存在边w(x,y))
g(x)g(y)deg(y)=0 g ( x ) − ∑ g ( y ) d e g ( y ) = 0

我们从1出发的,所以1号点的初始概率是1,但是这并不是最后的概率
因为起点是可以重复经过的,所以还会有再次到达的概率

那么在 f(w(x,y)) f ( w ( x , y ) ) 中:
f(w(u,x))out[w(u,x)]=g(x)dep(x) f ( w ( u , x ) ) ∗ o u t [ w ( u , x ) ] = g ( x ) d e p ( x )
f(w(y,v))out[w(y,v)]=g(y)dep(y) f ( w ( y , v ) ) ∗ o u t [ w ( y , v ) ] = g ( y ) d e p ( y )
(我们先要到达 w(x,y) w ( x , y ) 的一个端点,而从这个端点进入 w(x,y) w ( x , y ) 还有一定的概率)

我们就可以用高斯消元求解g了,再计算f
之后从小到大安排编号即可

注意:

  • 一号结点的初始概率为1,所以a[1][n+1]=-1

tip

其实还是有一点疑问:为什么概率要用高斯消元(求解未知量)呢?

#include<bits/stdc++.h>

using namespace std;

const double eps=1e-7;
const int N=502;
struct node{
    int x,y;
};
node e[150000];
int n,m,du[N];
double a[N][N],f[150000];

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

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&e[i].x,&e[i].y);
        du[e[i].x]++; du[e[i].y]++;
    }

    for (int i=1;i<=n;i++) a[i][i]=1.0;
    a[1][n+1]=1.0;                                //初始概率为1 
    for (int i=1;i<=m;i++) {
        int x=e[i].x,y=e[i].y;
        if (y!=n) a[x][y]-=1.0/(double)du[y];     //不是n结点 
        if (x!=n) a[y][x]-=1.0/(double)du[x];
    }
    gauss();
    for (int i=1;i<=m;i++) {
        int x=e[i].x;
        int y=e[i].y;
        if (x!=n) f[i]+=a[x][n+1]/(double)du[x];    
        if (y!=n) f[i]+=a[y][n+1]/(double)du[y];
    }
    sort(f+1,f+1+m);
    double ans=0;
    for (int i=1;i<=m;i++) 
        ans+=f[i]*(double)(m-i+1);
    printf("%0.3lf",ans);
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值