前几天一直纠结在《地下迷宫》那题中,苦苦思索无果。在一个月黑风高的夜晚,终于开窍,原来期望就是这么回事。赛后DK跟我说求期望很简单,我以为是诓我去做这个题目。我心说,您老人家看什么不简单啊,何况你还是期望控。现在觉得果然是简单的,用我的人品做担保,真的很简单。
先来看一个小例子:如图,从起点0,走到终点4。每次等概率地随机选择一条路走,(比如当前在0点,下一步分别有1/3的可能走到1,2,3点;对于1 点,下一步各有1/3的可能走到1点,0点,2点),求平均需要用几步走到终点4,也就是求走到4点所花步数的期望。
分析:
要从0走到4,从第一步的角度来看,无非是三种途径:
①先走到1,然后经过若干过程从1走到4;
②先走到2,然后经过若干过程从2走到4;
③先走到3,然后经过若干过程从3走到4。
假如我们已经知道了从1、2、3走到4所要花的平均步数分别是E(1)、E(2)、E(3),那么从0走到4的步数,有三种可能:E(1)+1、 E(2)+1、E(3)+1,而且这三种是等概率的。那么从0走到4的平均步数就是(E(1)+1+E(2)+1+E(3)+1)/3。现在我们并不知道 E(1)、E(2)、E(3)的值,得到的只是一个方程E(0)=(E(1)+E(2)+E(3))/3+1
同样的,对于点2,我们可以列出方程E(2)=(E(0)+E(1)+E(3)+E(4))/4+1。
有人说了,我靠,E(4)是什么东东?
E(4)当然就是从4点走到4点所花的平均步数,显然有E(4)=0。
联立方程组
E(0)=(E(1)+E(2)+E(3))/3+1
E(1)=(E(0)+E(1)+E(2))/3+1
E(2)=(E(0)+E(1)+E(3)+E(4))/4+1
E(3)=(E(0)+E(2)+E(4))/3+1
E(4)=0
图是我随手画的,求出来的解不整,保留两位小数如下:
E[0]=8.38
E[1]=9.14
E[2]=6.90
E[3]=6.10
E[4]=0.00
现在只要会编程解方程组就大功告成了。线性代数里讲过,可以用矩阵来表示线性方程组,用高斯消元法对矩阵进行处理,即可求解。有的小朋友没学线性代数,有 的和我一样学完后早忘到脚后跟去了。我是从网上搜了个高斯消元的代码来研究学习的。要不老衲买一赠一,顺道把高斯消元也解释一下吧。
-----------------------促销大派送之高斯消元---------------------------
上述方程组整理可得(为了减少精度损失,在某些式子上乘个常数,使系数整一点):
3*E(0) -E(1) -E(2) -E(3)+0*E(4)=3
-E(0)+2*E(1) -E(2)+0*E(3)+0*E(4)=3
-E(0) -E(1)+4*E(2) -E(3) -E(4)=4
-E(0)-0*E(1) -E(2)-3*E(3) -E(4)=3
0*E(0) +0*E(1) +0*E(2)+0*E(3) +E(4)=0
我们用一个5*5的二维数组a来描述等号左边各项的系数,用数组b来表示等号右边的常数项。直接用一个5*6的矩阵来表示当然也是可以的。数组的数据类型为double
a b
[0] 3 -1 -1 -1 0 | 3
[1] -1 2 -1 0 0 |3
[2] -1 -1 4 -1 -1| 4
[3] -1 0 -1 3 -1 | 3
[4] 0 0 0 0 1 | 0
注意,在我的叙述中,行号列号都是从0开始编的。
由方程组的性质,我们知道,可以给某一行乘上一个非零常数,可以把某一行乘一个非零常数加到另一行上去,可以任意调换两行的位置。这些操作都不影响方程组 的解,叫做行初等变换。我们利用行初等变换来处理矩阵。首先,我想让左边只留一个E(0),其他的E(0)系数都消掉。我们选择保留第0行,在第1、2、 3行上都加上第0行的1/3,矩阵变成了这样(只显示两位小数,下同):
3.00 -1.00 -1.00 -1.00 0.00| 3.00
0.00 1.67 -1.33 -0.33 0.00| 4.00
0.00 -1.33 3.67 -1.33 -1.00| 5.00
0.00 -0.33 -1.33 2.67 -1.00| 4.00
0.00 0.00 0.00 0.00 1.00| 0.00
第0行就固定不动了,接着我让下面四行只留一个E(1),其他的消掉。方法是在第2行上加上第1行的4/5,在第3行上加上第1行的1/5。处理后:
3.00 -1.00 -1.00 -1.00 0.00| 3.00
0.00 1.67 -1.33 -0.33 0.00| 4.00
0.00 0.00 2.60 -1.60 -1.00| 8.20
0.00 0.00 -1.60 2.60 -1.00| 4.80
0.00 0.00 0.00 0.00 1.00| 0.00
从上面的步骤可以看出,在消元的时候,我们需要从下面剩下的行中选择一个需要保留的行,这里称作主元行。选择的标准是什么?比如上一步,我们完全可以把第 1行加上第3行的5倍,把第2行加上第3行的-4倍。我看的那篇文章上说,选择该列中绝对值最大的那一行作为主元行,这样可以减少精度损失。好吧,我们相 信这个说法。假设a[1][1]这个位置不是1.67,而是0.67,那么这一列中绝对值最大的系数就是a[2][1]的-1.33(注意,第0行是不参 与比较的,它已经固定下来了)。那我们就交换第1、2行,把-1.33这一行提到上面,然后再执行消元操作,把这一列的下面都消成0。
如果处理到某一列的时候,发现这一列剩下的系数竟然全是0了,比如这样的:
1 2 3 4| 5
0 6 7 8| 9
0 0 0 1| 2
0 0 0 2| 4
第2列剩下的全是0了,找不到主元行了,这说明该矩阵的秩小于元数,方程组解不出唯一解来。通俗点说,就是这些方程里有水货,表面上看是4个方程,可仔细一瞧,第2行和第3行本质上是一样的,当然解不出。
好了,回到我们刚才的例子,通过一番处理之后,矩阵的左下角全变成0了。这称为行阶梯式。
3.00 -1.00 -1.00 -1.00 0.00| 3.00
0.00 1.67 -1.33 -0.33 0.00| 4.00
0.00 0.00 2.60 -1.60 -1.00| 8.20
0.00 0.00 0.00 1.62 -1.62| 9.85
0.00 0.00 0.00 0.00 1.00| 0.00
根据最后一行,可以确定E(4)的值,我们把它存到x[4]里。把E(4)代入到3式,可以解出E(3);把E(3)、E(4)代入2式,解出E(2)……
代码实现如下:
#define EPS 1e-10
int N;
#define MAX 100
double a[MAX][MAX],b[MAX],x[MAX];
bool flag;
double ab(double x)
{
return (x>=0)?x:-x;
}
void Gauss()
{
flag=1;
double maxi,d;
int index,i,j,k;
for(k=0;k<N;k++)
{
maxi=0;
for(i=k;i<N;i++)//找该列绝对值最大的行
{
if(ab(a[i ][k])>maxi)
{
index=i;
maxi=ab(a[i ][k]);
}
}
if(maxi<EPS)//如果剩下的全是0,失败返回
{
flag=0;
return;
}
if(index!=k)//把主元行交换到上面来
{
swap(b[index],b[k]);
for(j=k;j<N;j++)
swap(a[index][j],a[k][j]);
}
for(i=k+1;i<N;i++)//把非主元行的系数消掉
{
d=a[i ][k]/a[k][k];
b[i ]-=b[k]*d;
a[i ][k]=0;
for(j=k+1;j<N;j++)
a[i ][j]-=a[k][j]*d;
}
}
//至此,已经得到一个行阶梯式
for(i=N-1;i>=0;i--)//从底向上依次代入求解
{
for(j=i+1;j<N;j++)
b[i ]-=a[i ][j]*x[j];
x[i ]=b[i ]/a[i ][i ];
}
}
------------------------赠品End------------------------
高斯消元解方程组的复杂度是O(N^3),回到地下迷宫这题,对所有的连通点联立方程组。连通点的个数不超过100,代码见这里
http://bbs.zjut.com/viewthread.php?tid=1167909&page=1#pid6158985(链接已经失效)
扩展1:POJ 3682 King Arthur's Birthday Celebration
http://acm.pku.edu.cn/JudgeOnline/problem?id=3682
题目大意是说扔硬币,有p的概率是正面,1-p的概率是反面。每天扔一次,直到累计扔出k个正面为止。在扔硬币期间,第一天花1千块钱,第二天花3千,第三天花5千……
求平均扔几次会停止,平均会花多少钱。
以k=3,p=0.5为例,按已扔出几个正面来区分,共有4种状态。各状态之间按如下的概率转移。