标签:
概率和期望DP(整理)
概率DP顺着推,期望DP逆着递推求解
概率,又称或然率、机会率、机率(几率)或可能性,是概率论的基本概念。概率是对随机事件发生的可能性的度量,一般以一个在0到1之间的实数表示一个事件发生的可能性大小。越接近1,该事件更可能发生;越接近0,则该事件更不可能发生。人们常说某人有百分之多少的把握能通过这次考试,某件事发生的可能性是多少,这都是概率的实例。
期望就是加权平均。
1、期望值是指人们对所实现的目标主观上的一种估计;
2、期望值是指人们对自己的行为和努力能否导致所企求之结果的主观估计,即根据个体经验判断实现其目标可能性的大小;
3、期望值是指对某种激励效能的预测;
4.期望值是指社会大众对处在某一社会地位、角色的个人或阶层所应当具有的道德水准和人生观、价值观的全部内涵的一种主观愿望。
在概率和统计学中,一个随机变量的期望值(英文:expected value)(或期待值)是变量的输出值乘以其机率的总和,换句话说,期望值是该变量输出值的平均数。期望值并不一定包含于变量的输出值集合里。
概率DP
POJ3744
在一条布满地雷的路上,你现在的起点在1处。在N个点处布有地雷,1<=N<=10。地雷点的坐标范围:[1,100000000].
每次p的概率前进1步,1-p的概率前进2步。问顺利通过这条路的概率。就是不要走到有地雷的地方。 (0.25 ≤ p ≤ 0.75)
分析:
设dp[i]表示到达i点的概率,则 初始值 dp[1]=1.
很容易想到转移方程: dp[i]=p*dp[i-1]+(1-p)*dp[i-2];
但是由于坐标的范围很大,直接这样求是不行的,而且当中的某些点还存在地雷。
N个有地雷的点的坐标为 x[1],x[2],x[3]```````x[N].
我们把道路分成N段:
1~x[1];
x[1]+1~x[2];
x[2]+1~x[3];
`
`
`
x[N-1]+1~x[N].
这样每一段只有一个地雷。我们只要求得通过每一段的概率。乘法原理相乘就是答案。
对于每一段,通过该段的概率等于1-踩到该段终点的地雷的概率。
就比如第一段 1~x[1]. 通过该段其实就相当于是到达x[1]+1点。那么p[x[1]+1]=1-p[x[1]].
但是这个前提是p[1]=1,即起点的概率等于1.对于后面的段我们也是一样的假设,这样就乘起来就是答案了。
对于每一段的概率的求法可以通过矩阵乘法快速求出来。
http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710586.html
/* POJ 3744 C++ 0ms 184K */ #include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> #include<math.h> using namespace std; struct Matrix { double mat[2][2]; }; Matrix mul(Matrix a,Matrix b) { Matrix ret; for(int i=0;i<2;i++) for(int j=0;j<2;j++) { ret.mat[i][j]=0; for(int k=0;k<2;k++) ret.mat[i][j]+=a.mat[i][k]*b.mat[k][j]; } return ret; } Matrix pow_M(Matrix a,int n) { Matrix ret; memset(ret.mat,0,sizeof(ret.mat)); for(int i=0;i<2;i++)ret.mat[i][i]=1; Matrix temp=a; while(n) { if(n&1)ret=mul(ret,temp); temp=mul(temp,temp); n>>=1; } return ret; } int x[30]; int main() { int n; double p; while(scanf("%d%lf",&n,&p)!=EOF)//POJ上G++要改为cin输入 { for(int i=0;i<n;i++) scanf("%d",&x[i]); sort(x,x+n); double ans=1; Matrix tt; tt.mat[0][0]=p; tt.mat[0][1]=1-p; tt.mat[1][0]=1; tt.mat[1][1]=0; Matrix temp; temp=pow_M(tt,x[0]-1); ans*=(1-temp.mat[0][0]); for(int i=1;i<n;i++) { if(x[i]==x[i-1])continue; temp=pow_M(tt,x[i]-x[i-1]-1); ans*=(1-temp.mat[0][0]); } printf("%.7lf\n",ans);//POJ上G++要改为%.7f } return 0; }
POJ 2151
http://www.cnblogs.com/kuangbin/archive/2012/10/03/2711164.html
题意:ACM比赛中,共(M<=30)道题,T(1 < T <= 1000)个队,pij表示第i队解出第j题的概率问 每队至少解出一题且冠军队至少解出N (0 < N <= M)道题的概率。
解析:概率DP设dp[i][j][k]表示第i个队在前j道题中解出k道的概率则:dp[i][j][k]=dp[i][j-1][k-1]*p[j][k]+dp[i][j-1][k]*(1-p[j][k]);先初始化算出dp[i][0][0]和dp[i][j][0];设s[i][k]表示第i队做出的题小于等于k的概率则s[i][k]=dp[i][M][0]+dp[i][M][1]+``````+dp[i][M][k];则每个队至少做出一道题概率为P1=(1-s[1][0])*(1-s[2][0])*```(1-s[T][0]);每个队做出的题数都在1~N-1的概率为P2=(s[1][N-1]-s[1][0])*(s[2][N-1]-s[2][0])*```(s[T][N-1]-s[T][0]);最后的答案就是P1-P2
参考代码: #include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> #include<math.h> using namespace std; double dp[1010][50][50]; double s[1010][50]; double p[1010][50]; int main() { int M,N,T; while(scanf("%d%d%d",&M,&T,&N)!=EOF) { if(M==0&&T==0&&N==0)break; for(int i=1;i<=T;i++) for(int j=1;j<=M;j++) scanf("%lf",&p[i][j]); for(int i=1;i<=T;i++) { dp[i][0][0]=1; for(int j=1;j<=M;j++)dp[i][j][0]=dp[i][j-1][0]*(1-p[i][j]); for(int j=1;j<=M;j++) for(int k=1;k<=j;k++) dp[i][j][k]=dp[i][j-1][k-1]*p[i][j]+dp[i][j-1][k]*(1-p[i][j]); s[i][0]=dp[i][M][0]; for(int k=1;k<=M;k++)s[i][k]=s[i][k-1]+dp[i][M][k]; } double P1=1; double P2=1; for(int i=1;i<=T;i++) { P1*=(1-s[i][0]); P2*=(s[i][N-1]-s[i][0]); } printf("%.3lf\n",P1-P2); } return 0; }
期望DP
HDU 3853
http://www.cnblogs.com/kuangbin/archive/2012/10/03/2711140.html
题意: 有一个人被困在一个 R*C(2<=R,C<=1000) 的迷宫中,起初他在 (1,1) 这个点,迷宫的出口是 (R,C)。在迷宫的每一个格子中,他能花费 2 个魔法值开启传送通道。假设他在 (x,y) 这个格子中,开启传送通道之后,有 p_lift[i][j] 的概率被送到 (x,y+1),有 p_down[i][j] 的概率被送到 (x+1,y),有 p_loop[i][j] 的概率被送到 (x,y)。问他到出口需要花费的魔法值的期望是多少。解析:设dp[i][j]表示(i,j)到(R,C)需要魔法值则:dp[i][j]=p1[i][j]*dp[i][j]+p2[i][j]*dp[i][j+1]+p3[i][j]*dp[i+1][j]+2;化简得到:dp[i][j]=p2[i][j]*dp[i][j+1]/(1-p1[i][j])+p3[i][j]*dp[i+1][j]/(1-p1[i][j])+2/(1-p1[i][j]);注意一种情况就是p1[i][j]==1的情况。题目只是保证答案小于1000000.但是有的点可能永远都不可能到达的。所以这样的点出现p1[i][j]是允许的。否则就会WA了。
参考程序:
#include<stdio.h> #include<iostream> #include<algorithm> #include<string.h> #include<math.h> using namespace std; const int MAXN=1010; const double eps=1e-5; double dp[MAXN][MAXN]; double p1[MAXN][MAXN]; double p2[MAXN][MAXN]; double p3[MAXN][MAXN]; int main() { int R,C; while(scanf("%d%d",&R,&C)!=EOF) { for(int i=1;i<=R;i++) for(int j=1;j<=C;j++) scanf("%lf%lf%lf",&p1[i][j],&p2[i][j],&p3[i][j]); dp[R][C]=0; for(int i=R;i>=1;i--) for(int j=C;j>=1;j--) { if(i==R&&j==C)continue; if(fabs(1-p1[i][j])<eps)continue; dp[i][j]=p2[i][j]/(1-p1[i][j])*dp[i][j+1]+p3[i][j]/(1-p1[i][j])*dp[i+1][j]+2/(1-p1[i][j]); } printf("%.3lf\n",dp[1][1]); } return 0; }
POJ2096
http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710621.html
一个软件有s个子系统,会产生n种bug 某人一天发现一个bug,这个bug属于一个子系统,属于一个分类 每个bug属于某个子系统的概率是1/s,属于某种分类的概率是1/n 问发现n种bug,每个子系统都发现bug的天数的期望。 (0 < n, s <= 1 000)
求解:dp[i][j]表示已经找到i种bug,j个系统的bug,达到目标状态的天数的期望dp[n][s]=0;要求的答案是dp[0][0];
dp[n][s]=0的理解,因为已经n种bug,每个子系统都发现bug了。在这种情况下再发现bug,都是已经没有意义了。期望DP都是从末状态到最初状态。dp[i][j]可以转化成以下四种状态:dp[i][j],发现一个bug属于已经有的i个分类和j个系统。概率为(i/n)*(j/s);
dp[i][j+1],发现一个bug属于已有的分类,不属于已有的系统.概率为 (i/n)*(1-j/s);dp[i+1][j],发现一个bug属于已有的系统,不属于已有的分类,概率为 (1-i/n)*(j/s);dp[i+1][j+1],发现一个bug不属于已有的系统,不属于已有的分类,概率为 (1-i/n)*(1-j/s);
于是得到
dp[i][j]=dp[i][j]*(i/n)*(j/s)+dp[i][j+1]*(i/n)*(1-j/s)+dp[i+1][j]*(1-i/n)*(j/s)+dp[i+1][j+1]*(1-i/n)*(1-j/s)
整理得转移方程:
dp[i][j]=(dp[i][j+1]*i*(s-j)+dp[i+1][j]*(n-i)*j+dp[i+1][j+1]*(n-i)*(s-j))/(n*s-i*j)
参考代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int MAXN=1010;
double dp[MAXN][MAXN];
int main()
{
int n,s;
while(scanf("%d%d",&n,&s)!=EOF)
{
dp[n][s]=0;
for(int i=n;i>=0;i--)
for(int j=s;j>=0;j--)
{
if(i==n&&j==s)continue;
dp[i][j]=(i*(s-j)*dp[i][j+1]+(n-i)*j*dp[i+1][j]+(n-i)*(s-j)*dp[i+1][j+1]+n*s)/(n*s-i*j);
}
printf("%.4lf\n",dp[0][0]);//POJ上G++要改成%.4f
}
return 0;
}
HDU4336
有n种卡片,吃零食的时候会吃到一些卡片,告诉你在一袋零食中吃到每种卡片的概率,求搜集齐每种卡片所需要买零食的袋数的期望。
1 <= N <= 20;p1 + p2 + ... + pN <= 1;1s
(1)、容斥原理
http://blog.csdn.net/azheng51714/article/details/7829826
每个bag中什么卡的机会都有,说明每个概率都会有交集。
设卡片的分布p=(p1,p2,...,pn),T(p)表示拿到所有卡片时买的零食数目,有
由容斥原理得,
参考程序
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 22;
double p[maxn];
int main() {
int n,i,j;
while (~scanf("%d",&n))
{
for (i = 0; i < n; ++i) scanf("%lf",&p[i]);
double ans = 0.0;
//根据二项式定理C(n,0)+C(n,1) + ... + C(n,n) = 2^n
//所以这里2^n - 1种可能,枚举
for (i = 1; i < (1<<n); ++i)
{
int ct = 0;
double tmp = 0.0;
for (j = 0; j < n; ++j)
{
if (i&(1<<j))//检查0到n中存在于i状态的点
{
ct++;
tmp += p[j];
}
}
//鸽巢定理
if (ct&1) ans += 1.0/tmp;
else ans -= 1.0/tmp;
}
printf("%.4lf\n",ans);
}
return 0;
}
(2)、期望DP 状态压缩
http://www.cnblogs.com/Lyush/archive/2012/08/04/2623439.html
首先这题可以用期望DP来计算最后的期望值,由于这题每张卡片对应的概率是不相同的,所以不能像POJ-2096那样dp[i]表示拿到了i 张卡片来表示状态,而是要开一个 1<<N的状态来压缩状态表示拿到不用的卡片的期望值。
对于给定的N,我们有dp[(1<<N)-1]=0,因为这已经是最后的状态了。
对于dp[i] 我们需要分析其能够到达的状态:有两种(1)原状态 (2)新状态
如果 N=6, i 的二进制位为 011011。原状态:那么可能买零食不改变原来状态,也就是中了已经有的卡片或者是没有中卡片。新状态:状态i中只有1个0变成1的状态是可到达的状态。
(1)原状态概率myself =p[1]+p[2]+p[4]+p[5]+NONE,NONE表不出现卡片的概率
(2)新状态概率是dp[111011]*p[6]+dp[011111]*p[2]
对于一般情况得到:
dp[i]=dp[i]*myself+
整理得转移方程:
dp[i]=/(1-myself)
参考代码
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
int N;
double seq[25], dp[1100000], p[1100000];
void pre()
{
int LIM = 1 << N;
for (int i = 0; i < LIM; ++i) {
p[i] = 0;
for (int j = 0; j < N; ++j) {
if ((1 << j) & i) { // 低位对应编号小的概率
p[i] += seq[j+1];
}
}
}
}
int main()
{
int temp;
double none, tot, myself;
while (scanf("%d", &N) == 1) {
dp[(1<<N)-1] = none = 0;
for (int i = 1; i <= N; ++i) {
scanf("%lf", &seq[i]);
none += seq[i];
}
none = 1 - none;
pre();
for (int i = (1<<N)-2; i >= 0; --i) {//dp[(1<<N)-1]=0已知
tot = 0;
myself = p[i] + none;
for (int j = 0; j < N; ++j) {
if (!((1 << j)&i)) {
temp = i | (1 << j);
tot += seq[j+1] * dp[temp];
}
}
dp[i] = (tot + 1) / (1 - myself);
}
printf("%.6lf\n", dp[0]);
}
return 0;
}
标签:
原文地址:http://www.cnblogs.com/lizw0520/p/4292692.html