BZOJ3270 博物馆

题意:

给定一个无向连通图,有两个人分别在\(a\)\(b\)点,每个点有概率留在本地不动,剩下等概率移动到一个相邻的点。问他们在每个点相遇的概率。
其中,\(n\leq 20\)

知识点:

期望概率DP,高斯消元

解法:

首先我想讲一下为什么我想写这一篇题解,因为网上大多数其他的题解里面描述的都有一点很关键的问题,导致很多人看题解学习的时候都被误导了(包括我)。感谢wjgg教会了我这道题正确的理解方法。

其实错在哪里了呢?是因为他们把设的未知数设成了概率。其实大家只要仔细想一想,为什么起始状态的概率会\(>1\)呢?没有那种概率是可以大于1的啊!想到这里相信大家都已经知道了设概率其实是不对的了。

\(f_{u,v}\)表示甲走到\(u\)号点,乙走到\(v\)号点的期望次数(这里的期望可以理解为有无穷种路线,其中达到这种状态的平均次数),\(p_i\)是留在本地的概率,\(out_i\)是从某点出发的概率(这个\(out_i\)很好算,就用不留在本地的概率除以当前点的度数就可以了)。

可以列出状态转移方程:

\[f_{u,v}=p_u\times p_v\times f_{u,v}+\displaystyle\sum_{e(i,u)} out_i\times p_v\times f_{i,v} +\displaystyle\sum_{e(j,v)} out_j\times p_u\times f_{u,j}+\displaystyle\sum_{e(i,u)}\displaystyle\sum_{e(j,v)}out_i\times out_j\times f_{i,j} \]

其中必须确保上述方程中的右边满足\(i\not=j,i\not=u,i\not=v,j\not=u,j\not=v,u\not=v\)。因为只要到了一个\(u=v\)的点就碰面了,也就结束游走了。

特别地,初始所在的\(a\)\(b\)点右边期望要\(+1\),表示刚开始所在的地方必定经过一次。

然后高斯消元即可求出答案。

备注:

  1. 枚举\(u\)\(v\)的入点的时候,\(id\)的顺序不可以反,必须按照从\(u\)\(v\)(或者是\(i\)\(j\))这样的顺序,否则表示的就不是当前那个未知数。此处高斯消元的第\(i\)行理解为第\(i\)个方程。

  2. 为什么不能设概率而要设期望,是因为概率的和为\(1\)表示不出来,无法去重,所以列不出正确的方程。

  3. 为什么最终的答案概率\(=\)期望,是因为这些点只会经过\(1\)次,所以概率\(=\)期望。

代码:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn=410;
const double eps=1e-8;
int n,m,A,B,d[maxn],id[21][21],head[21],tot,num;
double a[maxn][maxn],p[21],out[21];
struct node
{
	int nxt,to;
}edge[maxn*maxn];

void add(int u,int v)
{
	edge[++tot]=(node){head[u],v};
	head[u]=tot;
}

void gauss(int N)
{
	int i,j,k,l,p;
	double tt;
	for (i=1,j=1;i<=N&&j<=N;i++,j++)
	{
		p=i;
		for (k=i+1;k<=N;k++)
			if (fabs(a[k][j])>fabs(a[p][j]))
				p=k;
		if (p!=i)
		{
			for (k=j;k<=N+1;k++)
				swap(a[i][k],a[p][k]);
		}
		if (fabs(a[i][j])<eps)
		{
			i--;
			continue;
		}
		tt=a[i][j];
		for (k=j;k<=N+1;k++)
			a[i][k]/=tt;
		for (k=i+1;k<=N;k++)
		{
			tt=a[k][j];
			for (l=j;l<=N+1;l++)
				a[k][l]-=tt*a[i][l];
		}
	}
	for (i=N;i>=1;i--)
		for (j=i+1;j<=N;j++)
			a[i][N+1]-=a[i][j]*a[j][N+1];
}

int main()
{
	int i,j,k,l,u,v;
	scanf("%d%d%d%d",&n,&m,&A,&B);
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
			id[i][j]=++num;
	for (i=1;i<=m;i++)
	{
		scanf("%d%d",&u,&v);
		d[u]++,d[v]++;
		add(u,v),add(v,u);
	}
	for (i=1;i<=n;i++)
	{
		scanf("%lf",&p[i]);
		out[i]=(1-p[i])/d[i];
	}
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
		{
			a[id[i][j]][id[i][j]]=-1;
			if (i!=j)
				a[id[i][j]][id[i][j]]+=p[i]*p[j];
			for (k=head[i];k;k=edge[k].nxt)
			{
				u=edge[k].to;
				if (u!=i&&u!=j)
					a[id[i][j]][id[u][j]]+=out[u]*p[j];
			}
			for (k=head[j];k;k=edge[k].nxt)
			{
				v=edge[k].to;
				if (v!=i&&v!=j)
					a[id[i][j]][id[i][v]]+=out[v]*p[i];
			}
			for (k=head[i];k;k=edge[k].nxt)
			{
				u=edge[k].to;
				for (l=head[j];l;l=edge[l].nxt)
				{
					v=edge[l].to;
					if (u!=v)
						a[id[i][j]][id[u][v]]+=out[u]*out[v];
				}
			}
		}
	a[id[A][B]][num+1]=-1;
	gauss(num);
	for (i=1;i<=n;i++)
		printf("%0.6lf ",a[id[i][i]][num+1]);
	puts("");
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/Ronald-MOK1426/p/12611679.html