前言
破事水一篇:我们为什么会用高斯消元来解决期望DP的问题
在之前的几篇字里我们确乎是提到了期望DP的,但只有博物馆一道题是要用到高斯消元的。那么这是不是一种特例呢?
Obviously not.
事实上,在期望与概率DP中引入高斯消元并不是突兀的,毫无关联的,而确乎是有实际需求的。因为在一类概率问题中,有的事件的概率是前后关联的。出于简化运算的目的,我们会列出方程来求解。这个时候我们自然需要利用高斯消元来便捷地解方程。
正文
(1)驱逐猪猡(USACO Hol)
题面见链接传送门
关键点:等概率选择路径,固定概率终止,有固定起始节点
我们可以考虑到对于节点
a
,假设其与节点
然后就是高斯消元一波乱搞
欸答案好像不太对?你有没有考虑过那个炸弹在起始节点原地爆炸的概率?
#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
到
完了。
完了?
真的完了。
#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;
}
别看了,真的完了。

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

被折叠的 条评论
为什么被折叠?



