BZOJ 3270: 博物馆 && 1778: 驱逐猪猡 【概率DP+高斯消元】

题目描述:

中文题面,不多解释。1778传送门 3270 传送门

(博物馆)题目分析:

也许很多人做概率题的时候都有种虚幻感。。感觉莫名其妙就得出一个期望、概率,一知半解。。。
所以我在这里仔细地剖析一下这个题的所谓概率是怎么得来的。

两人的起始点为A,B
每个人的操作有两种:pi的概率不动,1-pi的概率任意选择一个邻接点走过去
两人相遇为终止

先弄清楚我们要求的是什么:求两人在每间房间相遇的概率

做概率题要想做的踏实,首先得明白每个概率到底是什么意义。
f [ a ] [ b ] f[a][b] f[a][b]表示Petya出现在a点,Vasya出现在b点的状态

两人不停地走,最后在某一间房相遇,显然会有很多种不同的走法。
每一种走法中,f[a][b]这个状态可能没有出现,可能出现一次,也可能出现很多次
每一种走法都有一个走成当前路径的概率 P P P
∑ ( f [ a ] [ b ] ∗ P ) \sum( f[a][b]*P) (f[a][b]P)就是在所有情况中,出现f[a][b]这种状态的期望次数
又因为两个人走到同一个房间后就不会继续走下去,所以f[x][x]这样的状态在某种走法中要么不出现,要么只出现一次。
所以 ∑ ( f [ x ] [ x ] ∗ P ) \sum (f[x][x]*P) (f[x][x]P)就是在x号房间相遇的概率
也就是说,在这道题中,所有情况中f[x][x]状态的期望出现次数 = 两人在x房间相遇的概率

显然我们不能直接枚举所有情况算出 ∑ ( f [ a ] [ b ] ∗ P ) \sum( f[a][b]*P) (f[a][b]P)
(当然如果走很多步之后概率很小可以忽略不计那么也许暴力枚举或者spfa转移也可以?)
F [ a ] [ b ] = ∑ ( f [ a ] [ b ] ∗ P ) F[a][b]=\sum( f[a][b]*P) F[a][b]=(f[a][b]P),看一下它满足什么关系
每走到一个点,它都有概率会向旁边走或者不动,记d[x]为x的出度,e[x][y]为x,y是否连边:
F [ a ] [ b ] = F [ a ] [ b ] ∗ p a ∗ p b ∗ [ a ! = b ] + ∑ e [ x ] [ a ] , x ! = b F [ x ] [ b ] ∗ p b ∗ ( 1 − p a ) ∗ 1 d [ x ] + ∑ e [ y ] [ b ] , y ! = a F [ a ] [ y ] ∗ p a ∗ ( 1 − p b ) ∗ 1 d [ y ] + ∑ e [ x ] [ a ] , e [ y ] [ b ] , x ! = y F [ x ] [ y ] ∗ ( 1 − p a ) ∗ ( 1 − p b ) ∗ 1 d [ x ] ∗ 1 d [ y ] + [ a = = A & & b = = B ] \begin{aligned} F[a][b]=&F[a][b]*p_a*p_b*[a!=b]\\ +&\sum_{e[x][a],x!=b}F[x][b]*p_b*(1-p_a)*\frac 1{d[x]}\\ +&\sum_{e[y][b],y!=a}F[a][y]*p_a*(1-p_b)*\frac 1{d[y]}\\ +&\sum_{e[x][a],e[y][b],x!=y}F[x][y]*(1-p_a)*(1-p_b)*\frac 1{d[x]}*\frac 1{d[y]}\\ +&[a==A \&\& b==B] \end{aligned} F[a][b]=++++F[a][b]papb[a!=b]e[x][a],x!=bF[x][b]pb(1pa)d[x]1e[y][b],y!=aF[a][y]pa(1pb)d[y]1e[x][a],e[y][b],x!=yF[x][y](1pa)(1pb)d[x]1d[y]1[a==A&&b==B]
相当于就是期望次数互相转移,把 F [ a ] [ b ] F[a][b] F[a][b]看做变量,总共n2 个变量,n^2个方程
高斯消元解。
由于起始点在A,B,所以 F [ A ] [ B ] F[A][B] F[A][B]要额外+1,表示期望次数除了由其他点过来,还可以自己作为起始点(在每种情况中f[A][B]至少为1)。处理转移的时候详见代码。
网上有些博客把F[a][b]描述为概率,实际上是不准确的,应该表述为概率之和(对于这种表示方法详见3143游走)。
(在文末会对玄学的概率DP高斯消元做一个小总结)

#include
#include
#include
#define maxn 405
using namespace std;
using namespace std;
const double eps = 1e-10;
int n,m,A,B,cnt[22];
double a[maxn][maxn],p[22];
bool e[22][22];
#define p(x,y) (x-1)*n+y
void build(int x,int y)
{
	int u=p(x,y),v;
	a[u][u]=-1;
	for(int i=1;i<=n;i++) if(e[i][x])
		for(int j=1;j<=n;j++) if(e[j][y]&&i!=j){
			v=p(i,j);
			if(i==x&&j==y) a[u][u]+=p[i]*p[j];
			if(i==x&&j!=y) a[u][v]+=p[i]*(1-p[j])/cnt[j];
			if(i!=x&&j==y) a[u][v]+=(1-p[i])*p[j]/cnt[i];
			if(i!=x&&j!=y) a[u][v]+=(1-p[i])*(1-p[j])/cnt[i]/cnt[j];
		}
}
inline bool no(double x){return fabs(x)<eps;}
void Gauss(int N)
{
	for(int i=1;i<=N;i++){
		if(no(a[i][i])) for(int j=i+1;j<=N;j++) if(!no(a[j][i])) {swap(a[i],a[j]);break;}
		if(no(a[i][i])) continue;
		for(int j=1;j<=N;j++) 
			if(i!=j&&!no(a[j][i]))
			{
				double t=a[j][i]/a[i][i];
				for(int k=i;k<=N+1;k++) a[j][k]-=a[i][k]*t;
			}
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&A,&B);
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),e[x][y]=1,e[y][x]=1,cnt[x]++,cnt[y]++;
	for(int i=1;i<=n;i++) scanf("%lf",&p[i]),e[i][i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			build(i,j);
	a[p(A,B)][n*n+1]=-1;
	Gauss(n*n);
	for(int i=1,x;i<=n;i++) x=p(i,i),printf("%.6f ",a[x][n*n+1]/a[x][x]);
}

(驱逐猪猡) 题目分析:

一个点上:

  • (1-P/Q)的概率往旁边走
  • P/Q的概率爆炸

要求在每个点爆炸的概率
同样的,f[i]为某种走法中走到i点的次数,p为这种走法的概率, F [ i ] = ∑ ( f [ i ] ∗ p ) F[i]=\sum(f[i]*p) F[i]=(f[i]p)
表示所有情况中走到i点的期望次数
F [ i ] = ( ∑ e [ i ] [ j ] F [ j ] ∗ ( 1 − P / Q ) ∗ 1 d [ j ] ) + [ i = = 1 ] F[i]=(\sum_{e[i][j]}F[j]*(1-P/Q)*\frac 1{d[j]})+[i==1] F[i]=(e[i][j]F[j](1P/Q)d[j]1)+[i==1]
n个变量,n个方程,起始点为1,1号点的期望次数+1,高斯消元解
每走到一次i点,都有P/Q的概率在i点爆炸,其他概率继续走,那么在i点爆炸的所有情况的概率和就是F[i]*P/Q
(文末会对高斯消元做一个分析)

PS: 这题题面说误差不超过10^-6的答案会被接受实际上没有SPJ,必须输出9位小数,还要判一下会不会输出-0(机房大佬提醒)

#include
#include
#include
#define maxn 305
using namespace std;
using namespace std;
const double eps = 1e-13;
int n,m,d[maxn];
double a[maxn][maxn],P,Q;
bool e[maxn][maxn];
inline bool no(double x){return fabs(x)<eps;}
void Gauss(int N)
{
	for(int i=1;i<=N;i++){
		if(no(a[i][i])) for(int j=i+1;j<=N;j++) if(!no(a[j][i])) {swap(a[i],a[j]);break;}
		if(no(a[i][i])) continue;
		for(int j=1;j<=N;j++) 
			if(i!=j&&!no(a[j][i]))
			{
				double t=a[j][i]/a[i][i];
				for(int k=i;k<=N+1;k++) a[j][k]-=a[i][k]*t;
			}
	}
	for(int i=1;i<=N;i++) a[i][N+1]/=a[i][i];
}
int main()
{
	scanf("%d%d%lf%lf",&n,&m,&P,&Q),P=P/Q;
	for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),e[x][y]=1,e[y][x]=1,d[x]++,d[y]++;
	for(int i=1;i<=n;i++){
		a[i][i]=-1;
		for(int j=1;j<=n;j++) if(e[i][j])
			a[i][j]+=(1-P)/d[j];
	}
	a[1][n+1]=-1;
	Gauss(n);
	for(int i=1;i<=n;i++) printf("%.9f\n",no(a[i][n+1]*P)?0:a[i][n+1]*P);
}

好了,在我们迷迷糊糊地用高斯消元解掉了两个看似无法解决的问题之后,我们来思考一下高斯消元解期望的原理到底是什么。
首先,以方程组的视角看,这个方程组有一个前提条件:
有常数项
也许读者会发现,在方程组中,唯一的常数项只有作为起点的+1(移项之后是-1)
也就是说,这对应了我们的图中必须有起点
再想,一个明明有无穷多种情况的东西,为什么可以求出一个最终的概率呢?
概率流失
在方程中,有一些项并不会将所有情况都往旁边转移,而会“吃掉一些情况”
举个例子,就是3270博物馆的两点相遇,F[x][x]不会再去对其他变量做贡献
还有就是1778驱逐猪猡的P/Q的概率爆炸,F[a][b]只会将它的(1-P/Q)贡献出去。
也就是说,有一些变量输入>输出,这造成了概率越乘越小,总和收敛于那些变量,这对应了我们的图中必须有终点

假设没有起点,也就是说没有常数项,显然方程无解或无数多解,因为最终的f[a][b] (期望次数)为0
假设没有终点,也就是说每个点的输出都是完整的概率“1”,那么将所有方程加起来,会得到0=-1,无解,因为最终的f[a][b]是不收敛的,趋近于 ∞ \infty

这样的话,起点输入1,终点接收,概率收敛,就可以求期望了。
可以看出,把所有终点的F加起来一定=1。

你可能感兴趣的:(概率DP)