如何使用高斯消元解决期望DP

本文探讨了在概率DP中为何会使用高斯消元,通过实例分析了如何借助高斯消元解决期望DP问题,涉及题目包括驱逐猪猡(USACO Hol)、游走(HNOI2013)和Xor和路径(HNOI2001),并指出在处理这类问题时起始和终止节点的特殊性。

前言

破事水一篇:我们为什么会用高斯消元来解决期望DP的问题

在之前的几篇字里我们确乎是提到了期望DP的,但只有博物馆一道题是要用到高斯消元的。那么这是不是一种特例呢?
Obviously not.
事实上,在期望与概率DP中引入高斯消元并不是突兀的,毫无关联的,而确乎是有实际需求的。因为在一类概率问题中,有的事件的概率是前后关联的。出于简化运算的目的,我们会列出方程来求解。这个时候我们自然需要利用高斯消元来便捷地解方程。

正文

(1)驱逐猪猡(USACO Hol)
题面见链接传送门
关键点:等概率选择路径,固定概率终止,有固定起始节点
我们可以考虑到对于节点 a ,假设其与节点b相邻且 b 的度为d[b],那么对于状态 f[a] (即在 a 爆炸的概率),b的贡献为(1f[b])1d[b]
然后就是高斯消元一波乱搞
欸答案好像不太对?你有没有考虑过那个炸弹在起始节点原地爆炸的概率?

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
    int i=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch);ch=getchar())
        if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar())
        i=(i<<3)+(i<<1)+(ch^48);
    return i*f;
}
int buf[1024];
inline void write(int x){
    if(!x){putchar('0');return ;}
    if(x<0){putchar('-');x=-x;}
    while(x){buf[++buf[0]]=x%10,x/=10;}
    while(buf[0]) putchar(buf[0]--);
    return ;
}
#define stan 333
#define sten 55555
int n,m,q,a[sten],b[sten];
double p,mat[stan][stan],d[sten],ans[stan];
inline void gauss(){
    for(int i=1;i<=n;++i){
        int maxn=i,j;
        for(j=i;!mat[j][maxn]&&j<=n;++j);
        swap(mat[j],mat[maxn]);
        for(int j=1;j<=n;++j)
            if(j!=maxn){
                double t=mat[j][maxn]/mat[maxn][maxn];
                for(int k=1;k<=n+1;++k)
                    mat[j][k]-=t*mat[maxn][k];
            }
    }
    return ;
}
signed main(){
    n=read();m=read();p=read();q=read();
    mat[1][n+1]=p=min(1.0,p*1.0/q);
    for(int i=1;i<=m;++i){
        a[i]=read();b[i]=read();
        ++d[a[i]];++d[b[i]];
    }
    for(int i=1;i<=m;++i){
        mat[a[i]][b[i]]-=(1-p)/d[b[i]];
        mat[b[i]][a[i]]-=(1-p)/d[a[i]];
    }
    for(int i=1;i<=n;++i) ++mat[i][i];
    gauss();
    for(int i=1;i<=n;++i)
        printf("%.9lf\n",mat[i][n+1]/mat[i][i]);
    return 0;
}

(2)游走(HNOI2013)
传送门
关键点:等概率选择路径,有固定起始与终止节点
这个题最主要的就是计算经过边的期望
对于一个边的两个端点 a,b 设经过的概率分别为 f[a],f[b] ,其度数分别为 d[a]d[b] ,则该边的经过次数为 f[a]/d[a]+f[b]/d[b]
这样我们就把边的期望转化为了点的期望。
然后又是一波无脑高斯消元
当然我相信你还知道对起始节点与终止节点有特殊处理的
貌似只能过B站的非官方数据

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
//#define double long double
#define eps 1e-7
using namespace std;
inline int read(){
    int i=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch);ch=getchar())
        if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar())
        i=(i<<3)+(i<<1)+(ch^48);
    return i*f;
}
int buf[1024];
inline void write(int x){
    if(!x){putchar('0');return ;}
    if(x<0){x=-x;putchar('-');}
    while(x){buf[++buf[0]]=x%10,x/=10;}
    while(buf[0]) putchar(buf[buf[0]--]+48);
    return ;
}
#define stan 555
#define sten 222222
int n,m,a[sten],b[sten];
double d[stan],mat[stan][stan],mean[sten],ans;
inline void addedge(int a,int b){
    if(a!=n) mat[b][a]-=1.0/d[a];
    return ;
}
inline void gauss(){
    for(int i=1;i<=n;++i){
        int maxn=i;int j;
        for(j=i;!mat[j][maxn]&&j<=n;++j);
        swap(mat[j],mat[maxn]);
        for(int j=1;j<=n;++j)
            if(j!=maxn){
                double t=mat[j][maxn]/mat[maxn][maxn];
                for(int k=1;k<=n+1;++k)
                    mat[j][k]-=t*mat[maxn][k];
            }
    }
}
signed main(){
    n=read();m=read();
    for(int i=1;i<=m;++i){
        a[i]=read();b[i]=read();
        ++d[a[i]];++d[b[i]];
    }
    for(int i=1;i<=m;++i){
        addedge(a[i],b[i]);
        addedge(b[i],a[i]);
    }
    for(int i=1;i<=n;++i)
        ++mat[i][i];
    for(int i=1;i<=n+1;++i)
        if(i<n) mat[n][i]=0;
        else mat[n][i]=1;
    ++mat[1][n+1];
    gauss();
    for(int i=1;i<=m;++i){
        if(a[i]!=n&&fabs(mat[a[i]][a[i]])>eps) mean[i]+=(mat[a[i]][n+1]/mat[a[i]][a[i]])/d[a[i]];
        if(b[i]!=n&&fabs(mat[b[i]][b[i]])>eps) mean[i]+=(mat[b[i]][n+1]/mat[b[i]][b[i]])/d[b[i]];
    }
    sort(mean+1,mean+m+1);
    for(int i=1;i<=m;++i)
        ans+=(m-i+1)*mean[i];
    printf("%.3lf",ans);
    return 0;
}

(3)Xor和路径(HNOI2001)
传送门
关键点:异或(按位处理),固定起点与终点
f[x] 表示从 x n异或和为 1 的期望
完了。
完了?
真的完了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
    int i=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch);ch=getchar())
        if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar())
        i=(i<<3)+(i<<1)+(ch^48);
    return i*f;
}
int buf[1024];
inline void write(int x){
    if(!x){putchar('0');return ;}
    if(x<0){putchar('-');x=-x;}
    while(x){buf[++buf[0]]=x%10,x/=10;}
    while(buf[0]) putchar(buf[0]--);
    return ;
}
#define stan 111
#define sten 11111
int n,m,a[sten],b[sten],c[sten],d[sten];
double mat[stan][stan],ans;
inline void gauss(){
    for(int i=1;i<=n;++i){
        int maxn=i;int j;
        for(j=i;!mat[j][maxn]&&j<=n;++j);
        swap(mat[j],mat[maxn]);
        for(int j=1;j<=n;++j)
            if(j!=maxn){
                double t=mat[j][maxn]/mat[maxn][maxn];
                for(int k=1;k<=n+1;++k)
                    mat[j][k]-=t*mat[maxn][k];
            }
    }
    return ;
}
inline double calc(int x){
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n+1;++j)
            mat[i][j]=0;
    for(int i=1;i<=m;++i){
        if(c[i]&x){
            ++mat[a[i]][b[i]];
            ++mat[a[i]][n+1];
            if(a[i]!=b[i]){
                ++mat[b[i]][a[i]];
                ++mat[b[i]][n+1];
            }
        }else{
            --mat[a[i]][b[i]];
            if(a[i]!=b[i])
                --mat[b[i]][a[i]];
        }
    }
    for(int i=1;i<=n;++i)
        mat[i][i]+=d[i];
    for(int i=1;i<=n+1;++i)
        if(i==n) mat[n][i]=1;
        else mat[n][i]=0;
    gauss();
    return mat[1][n+1]/mat[1][1];
}
signed main(){
    n=read();m=read();
    for(int i=1;i<=m;++i){
        a[i]=read();b[i]=read();c[i]=read();
        ++d[a[i]];if(a[i]!=b[i])++d[b[i]];
    }
    for(int i=1;i<(1<<30);i<<=1)
        ans+=i*calc(i);
    printf("%.3lf",ans);
    return 0;
}

别看了,真的完了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值