下面是对一系列求概率&期望问题的总结:
问题的形式一般是求一个事件发生的概率或者期望,而概率和期望的求解形式是有些区别的,一般方法是dp&记忆化搜索,特殊情况可能用到人肉消元 & 高斯消元。
如果是概率类的问题,那么求的方法一般是利用这个事件之前的概率,dp[i]的含义是i事件发生的概率(当然这里不一定是一维数组,写一维是为了简单起见,思路是一样的,下同)
大概的流程是 dp[i] = sigma(p[j] * dp[j]),j是可能转移到i的事件
即当前事件发生的概率是通过“可能转移到当前事件的事件”推过来的
初始状态的dp[i] = 1
而对于期望类的问题,求的方法一般是利用这个事件之后的概率,dp[j]表示从j状态转移到终止状态的期望
那么要求的其实就是从起始状态转移到终止状态的期望,即dp[s]
转移和求概率相反,是从“当前事件可能转移到的后续事件”推到当前事件,也就是倒推的一个过程
也就是dp[i] = sigma(p[j] * (dp[j] + cost)),j是i的后续状态,由于是求期望,所以要加上转移所消耗的步数cost
初始状态的话就是终止状态的dp[t] = 0
这样一般情况的概率&期望dp都可以解决了,最后会放一些这类可以直接解决的问题。
但是很多时候直接dp是解决不了问题的,为什么呢?因为状态之间的关系可能会比较复杂
比如求概率的时候,你在求某一个状态的时候可能无法确定所有需要知道的状态的概率。比如A可以转移到B,B也可以转移到A,那么求A的概率的时候需要知道B的概率,求B的概率的时候需要知道A的概率,这下就两个都求不出来了。
解决的方法就是先设这些概率为变量,然后建立相互之间关系的方程,最后消元得出答案
大概思路就是:每个转移方程可以构成一个包含方程,变量为各个状态的概率,这样就是n个未知数n个方程,理论上可以用高斯消元求出每个未知数
那么这种转移方程嵌套的问题也就能解决了
之前说过还有一种方法叫做人肉消元:这种方法是在高斯消元复杂度过高时候或者变量间关系不那么复杂时候运用的方法,比高斯消元能更快的得到答案。
具体是怎么搞呢?一般dp方程的形式是这样的:
dp[i] = sigma(A * dp[j]) + B * dp[i] + C
这里A,B,C都是常数,然后j是除了i之外的其他转移状态
我们发现,如果知道了所有的dp[j],那么dp[i] = (sigma(A * dp[j]) + C) / (1-B) 就解出来了
那问题就转换成怎么在之前所说转移嵌套的情况下得到dp[j]们
我们把某一个dp状态(比如dp[0])作为变量,使得每个dp状态的表示形式变成这样:
dp[i] = A*dp[0] + B * dp[i] + C
这样就可以从dp[0]的后续状态们开始推出所有的状态的表示,然后用已知数值的状态(求概率时是初始状态,求期望时是末尾状态)就可以轻松得到dp[0],那么根据这个表示法所有状态都可以求出来了
剩下要做的就是怎么维护A,B,C这三个常数了。这个在基本转移方程的基础上只要用这种表达形式稍微写一下很容易就能得出写法,就是在思维的过程可能会相对较多一些。
由于这个方法实质是通过人脑构造的方法进行消元,因此我称之为”人肉消元“(笑)
这种方法非常优美相比高斯消元也很快,但从做题速度的角度来说,思维的速度和敲模板的速度还是见仁见智吧。
下面给一些这类的题目:
高斯消元:
HDU 4418 Time Travel
#include
#include
#include
#include
#include
#include
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define eps 1e-9
const int MAXN=220;
double a[MAXN][MAXN],x[MAXN];//方程的左边的矩阵和等式右边的值,求解之后x存的就是结果
int equ,var;//方程数和未知数个数
int Gauss()
{
int i,j,k,col,max_r;
for(k=0,col=0;kfabs(a[max_r][col]))
max_r=i;
if(fabs(a[max_r][col]) q;
q.push(u);
k = 0 ;
has[u] = k ++;
while(!q.empty()){
u = q.front();
q.pop();
if(vis[u]) continue;
vis[u] = 1;
if(u == t || u == n-t)
{
a[has[u]][has[u]] = 1;
x[has[u]] = 0;
flg = 1;
conti