期望/概率dp入门+题单

期望dp几种常见设转移方程数组的方法

1、设f [ i ]表示的是由i 状态变成 最终状态的期望 (由末状态逆推)

2、按照题意直接设

3、把选择的东西加入数组,如f [ i ] [ j ]表示第i个物品选j个的期望
或f [ i ] [ j ]表示有i个A 物品,j个B物品的期望
(结合第一种的话就是,dp[i]j[j]:已经有i个A ,j个B 离达到最终状态还差多少期望)

求转移方程
先考虑逆向。(从最终状态的解开始逆推)
如果逆向没有思路,则考虑正向。

(一般而言,末状态已经决定了的话,就是逆向递推了)
也可以先看看边界在哪边,从边界开始递推转移。

例题:

Favorite Dice SPOJ - FAVDICE

题意:给定一个n面的骰子,问投出n个不同的面的期望投掷次数。 (1<=n<=1000)

分析:先考虑逆向转移,dp[n]=0 表示投出n个不同面之后,要达到投出n个不同面的状态还需要投掷0次。

即:dp[i]表示已经投出了i个面,要投出剩余n-i面的期望次数。

对于当前状态为i,投一次骰子,有i/n的可能投中已经出现的i个面之一,此情况下还需要投dp[i]次
					   有(n-i)/n的可能投出其余n-i面。此情况下还要投dp[i+1]次
即:由于投出的面可能出现过,也可能没出现过,所以dp[i]由dp[i] 与 dp[i+1] 转移而来
dp[i]=dp[i]*(i/n) + dp[i+1]*((n-i)/n) +1   (+1是因为要投一次骰子才能转移)

移项变成:dp[i]=dp[i+1] + n/(n-i)
#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 2e3+5;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//给定n面的骰子 求投的期望次数 满足每面都至少出现一次
double dp[maxn];//dp[i] 表示已经出现i个面时  投出剩余n-i面的期望次数 dp[n]=0
//再投一次 i/n的几率投中已经有的面,此情况下还需要投dp[i]次骰子,(n-i)/n几率投中新的面  此情况下还要投dp[i+1]次
//dp[i]=(i/n)dp[i] + (n-i)/n *dp[i+1] +1
//(n-i)/n * dp[i]= (n-i)/n *dp[i+1] +1
//dp[i]=dp[i+1] + n/(n-i)
int main()
{
    int t;scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        dp[n]=0.0;
        for(int i=n-1;i>=0;--i)
        {
            dp[i]=dp[i+1] + 1.0*n/(n-i) ;
        }
        printf("%.2f\n",dp[0]);
    }
    return 0;
}

也可以顺推,dp[i]表示投出i个面的期望次数,
dp[i] = dp[i-1] + n/(n-(i-1)) , 也就是投出i-1面的期望加上投一个新面的期望。

#include
#include
#include
using namespace std;
double dp[1005];
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++)
            dp[i]=dp[i-1]+(double)n/(double)(n+1-i);
        printf("%.2f\n",dp[n]);
    }
    return 0;
}


Kids and Prizes SGU - 495

题意:n个空白格子,随机选一个涂色,一共涂m次,问最终被涂色格子数目的期望。
(1<=n,m<=1e5)

分析:这题考虑逆向的话行不通。
而第i次涂色可由第i-1次转移而来,所以正向转移特别好想。

dp[i]表示涂了i次 被涂色格子数目期望
dp[i]由dp[i-1]转移而来,第i次涂色: dp[i-1]/n的概率涂中已经被涂色的格子,这种情况下答案仍是dp[i-1]
							      (n-dp[i-1])/n的概率涂中空白格子,这种情况下答案是dp[i-1]+1
dp[i]=(dp[i-1]/n) *dp[i-1] + (n-dp[i-1])/n * (dp[i-1]+1)

细节是这题误差要求1e-9,所以记得保留9位小数点输出

#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e5+5;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//n个格子  每次随机给一个染色 问m次染色后被涂上色的格子期望个数
//dp[i] i次染色后 涂色格子的期望个数 dp[1]=1
//第i次染色 (dp[i-1]/n)的几率涂中已经有颜色的格子,这种情况下仍然是dp[i-1] , (n-dp[i-1])/n 的几率涂中新格子
//dp[i]=(dp[i-1]/n)*dp[i-1] + (n-dp[i-1])/n *(dp[i-1]+1)
int n,m;
double dp[maxn];
int main()
{
    int t;t=1;
    while(t--)
    {
        scanf("%d %d",&n,&m);
        dp[1]=1.0;
        double num=n*1.0;
        for(int i=2;i<=m;i++)
        {
            dp[i]=(dp[i-1]/num)*dp[i-1] + (num-dp[i-1])/num * (dp[i-1]+1);
        }
        printf("%.9f\n",dp[m]);
    }
    return 0;
}

King Arthur’s Birthday Celebration POJ - 3682

题意:投一个硬币,第n次投的代价是2*n-1, 投中正面朝上的概率为给定的p,
投到k次正面朝上就停止 。 问投掷次数的期望,代价的期望。

分析:这题同样从正向开始考虑

E[i]表示投到i次正面朝上的次数期望, 当前投掷有p的概率是正面,1-p的概率是反面
E[i] = p*(E[i-1]) + (1-p)*(E[i]) + 1 
化简一下就是E[i]=1/p+E[i-1]  ,即:E[i]=i/p
(也可以这样理解,1次正面朝上的期望是1/p,每次投掷都是独立事件,所以i次正面朝上的期望次数就是i*1/p)


f[i]表示i次正面朝上的代价期望。
当前投掷有p概率是正面,这种情况下 当前投掷是第dp[i-1]+1次投掷,代价是2*(E[i-1]+1)-1
有1-p的概率是反面,此时的投掷是第dp[i]+1次投掷,代价是2*(E[i]+1)-1

f[i]=p*(f[i-1]+2*(E[i-1]+1)-1) + (1-p)*(f[i]+2*(E[i]+1)-1)
化简一下就是:f[i]=(f[i-1]+2*(E[i-1]+1)-1) +(1-p)/p * ( 2*(E[i]+1)-1 )
#include
#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e3+5;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//每天丢硬币 p的概率正面  出现k次正面就停止丢硬币
//double dp[maxn];
//出现i次正面 投掷次数期望
//dp[i]=p*dp[i-1] + (1-p)*dp[i] +1  <==> dp[i]=dp[i-1]+1/p   dp[i]=k/p
double ans1;
double p;
int k;
double f[maxn];//i次正面向上的花费
double E[maxn];
//f[1]=k/p 
//f[i]=p*(f[i-1]+2*(dp[i-1]+1)-1) + (1-p)*(f[i]+2*(dp[i]+1)-1)
//p*f[i]=p*(f[i-1]+2*(dp[i-1]+1)-1) + (1-p)*( 2*(dp[i]+1)-1 )
//f[i]=(f[i-1]+2*(dp[i-1]+1)-1) +(1-p)/p * ( 2*(dp[i]+1)-1 )
//如果抛出正面 当前这个是第dp[i-1]+1次投掷  等差数列中的第dp[i-1]+1项
//如果抛出反面  当前这个就是dp[i]+1次投掷
int main()
{
//    int t;scanf("%d",&t);
    while(~scanf("%d",&k) && k)
    {
        scanf("%lf",&p);
        f[0]=0,E[0]=0;
		for(int i=1;i<=k;i++)
        {
            E[i]=1/p+E[i-1];
            f[i]=(f[i-1]+2*(E[i-1]+1)-1) +(1-p)/p * ( 2*(E[i]+1)-1 );
        }
        printf("%.3f %.3f\n",E[k],f[k]);
    }
    return 0;
}

Collecting Bugs POJ - 2096

题意:一共有s个系统 ,共有n种bug, 每天可以找到一个bug(发生在每个系统的概率为1/s, 每种bug出现的概率为1/n)。 问每个系统都至少找到1个bug 且每种bug都被发现的期望天数 。

分析:这题和第一题投骰子,每面至少出现一次的思路是一样的,都是逆推。

dp[i][j]:在i个系统中出现了j种bug的状态 要到达 在s个系统中出现n种bug的剩余期望天数。
dp[s][n]=0;
每天找到的那个bug有4种情况,分别考虑进去就可以了。
#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e3+5;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//s个系统  共有n种bug  每天找到一个bug(每个系统1/s  每种bug 1/n) 问每个系统都找到bug  且每种bug都被发现的期望天数
//这题和投骰子每个面都出现的dp方法类似 
//dp[i][j] 出现了i个系统 j种bug的状态 要到达出现s个系统 n种bug的剩余期望天数
//dp[s][n]=0
//dp[i][j]=i/s*j/n *dp[i][j]   (没有出现在新系统 不是新bug)
//+(s-i)/s*j/n * dp[i+1][j]   (出现在新系统 不是新bug)
//+(n-j)/j* i/s * dp[i][j+1]  (没有出现在新系统 是新bug)
//+(s-i)/s * (n-j) /n *dp[i+1][j+1] (出现在新系统 是新bug)
// + 1  bug出现当天的天数
int n,s;
double dp[maxn][maxn];
int main()
{
    while(~scanf("%d %d",&n,&s))
    {
        dp[s][n]=0;
        for(int i=s;i>=0;i--)
        {
            for(int j=n;j>=0;j--)
            {
                if(i==s && j==n) continue;
                dp[i][j]=1.0;
                dp[i][j]+=(1.0*s-i)*j / (1.0*s*n) *dp[i+1][j];
                dp[i][j]+=(1.0*n-j)*i /  (1.0*n*s) *dp[i][j+1];
                dp[i][j]+=(1.0*s-i)*(1.0*n-j)/ (1.0*s*n) * dp[i+1][j+1];
                dp[i][j]/=(1.0-1.0*(i*j)/(s*n));
            }
        }
        printf("%.4f\n",dp[0][0]);
    }
    return 0;
}

Aeroplane chess HDU - 4405

题意:给定一段连续区间的格子[0,n],从0出发,每次投骰子,1~6等概率,投中i前进i格,另有m个格子标了数x[i] 到了这个格子可以立刻传送到第x[i]格,x[i]>i 。 问到达或超过第n格的期望投掷次数。
(1

分析:对于这种最终状态已经决定了的问题,基本就确定是逆向递推了。

dp[i]表示已经到达i,要到达最终状态(n或超过n)的期望次数。
dp[n]=dp[n+1]...=dp[n+6]=0;
当i格子没有标x[i],dp[i]=(dp[i+1]+d[i+2]...+dp[i+6])/6 +1;
否则 dp[i]=dp[x[i]]
ans=dp[0]
#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e5+10;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//[0,n]的格子区间  从0出发 ,投骰子前进步数,某些格子i上 可以直通x[i] 问到达或超过n的期望投掷次数
int n,m;
int x[maxn];//
//dp[i] 已经到达i 到达超过n的期望投掷次数
//dp[n]=0
//dp[i]=dp[x[i]]  x[i]!=0
//dp[i]=(dp[i+1]+dp[i+2]...+dp[i+6])/6  + 1
double dp[maxn];
int main()
{
    while(~scanf("%d %d",&n,&m) && n+m)
    {
        memset(x,0,sizeof x);
        for(int i=n;i<=n+6;i++) dp[i]=0.0;
        for(int i=1;i<=m;i++)
        {
            int a,y;
            scanf("%d %d",&a,&y);
            x[a]=y;
        }
        for(int i=n-1;i>=0;i--)
        {
            if(x[i]) dp[i]=dp[x[i]];
            else 
            dp[i]=(dp[i+1]+dp[i+2]+dp[i+3]+dp[i+4]+dp[i+5]+dp[i+6])/6.0 +1.0;
        }
        printf("%.4f\n",dp[0]);
    }
    return 0;
}

P1850 [NOIP2016 提高组] 换教室

题意:给定带边权的无向图,默认了一条路径c[1] c[2]…c[n] ,路径每个点又有一个备用点d[i], 可以选定不超过m个点,让c[i]以p[i]的概率变成d[i] , 问如何选择可以使得最终路径的期望最短路最小 。
(图的顶点<=300)

分析:先用floyd跑出最短路。
dp[i][j][0/1]: 走到路径第i个点,已经选了j个点变成d[i],第i个点没选/选 状态下的最优解。
边界dp[1][1][1]=dp[1][0][0]=0 ,然后j=0的边界也都先处理一下。

转移过程不难想:
期望/概率dp入门+题单_第1张图片

#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 3e2+10;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const double INF = 1e17+7;
//给定带边权的无向图 默认了一条n个点组成的路径  c[1] c[2]...c[n]从出发点到目的  
//开始前 可以选路径上任意m个点 让它以p[i]概率变成d[i] 使得路径和最小
int n,m,v,e;
int c[maxn*10],d[maxn*10];//第i个时间段的 
double dist[maxn][maxn];//多元最短路
double p[maxn*10];
double dp[maxn*10][maxn*10][2];//走到第i个时间段 用了j次修改机会  第i个是否使用修改 情况下 最优解
//dp[1][0][0]=0 
//dp[i][j][0]=min(dp[i-1][j][0]+dist[c[i-1]][c[i]] , (dp[i-1][j][1]+p*dist[d[i-1]][c[i]]+(1-p)*dist[c[i-1]][c[i]] )
//dp[i][j][1]=min(dp[i-1][j-1][0]+p*dist[c[i-1]][d[i]] + (1-p)*dist[c[i-1]][c[i]] ,
// dp[i-1][j][1]+4种排列组合)
void floyd()
{
	for(int k=1;k<=v;k++)
	{
		for(int i=1;i<=v;i++)
		{
			for(int j=1;j<=v;j++)
			{
				dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
			}
		}
	}
	for(int i=0;i<=v;i++) dist[i][i]=0.0;
}
int main()
{
    fill(dist[0],dist[0]+maxn*maxn,INF);
	scanf("%d %d %d %d",&n,&m,&v,&e);
	for(int i=1;i<=n;i++) scanf("%d",c+i);
	for(int i=1;i<=n;i++) scanf("%d",d+i);
	for(int i=1;i<=n;i++) scanf("%lf",p+i);
	for(int i=1;i<=e;i++)
	{
		int a,b,w;
		scanf("%d %d %d",&a,&b,&w);
		dist[a][b]=min(1.0*w,dist[a][b]);
		dist[b][a]=min(1.0*w,dist[a][b]);
	}
	floyd();
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			dp[i][j][0]=dp[i][j][1]=INF;//剔除不合法状态
		}
	}
	dp[1][0][0]=0;
	dp[1][1][1]=0;
	for(int i=2;i<=n;i++)
	{
		dp[i][0][0]=dp[i-1][0][0]+dist[c[i-1]][c[i]];//j==0的边界
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			//当前点不选
			dp[i][j][0]=min(dp[i-1][j][0]+dist[c[i-1]][c[i]],
							dp[i-1][j][1]+p[i-1]*dist[d[i-1]][c[i]]+(1-p[i-1])*dist[c[i-1]][c[i]]);
			//当前点选
			dp[i][j][1]=min(dp[i-1][j-1][0]+p[i]*dist[c[i-1]][d[i]] + (1-p[i])*dist[c[i-1]][c[i]],
							dp[i-1][j-1][1]+p[i]*p[i-1]*dist[d[i-1]][d[i]] + (1-p[i])*(1-p[i-1])*dist[c[i-1]][c[i]]
							+p[i]*(1-p[i-1])*dist[c[i-1]][d[i]] 
							+p[i-1]*(1-p[i])*dist[d[i-1]][c[i]]);
		}
	}
	double ans=INF;
	for(int i=0;i<=m;i++) ans=min(ans,min(dp[n][i][0],dp[n][i][1])); 
	printf("%.2f\n",ans);
    return 0;
}

Ilya and Escalator CodeForces - 518D

题意:n个人排队进电梯 , 每一秒队首有p概率进电梯 , 一次只能进一个人, 且只能按排队顺序进 问t秒后电梯里的人数期望。
(1<=n,t<=2e3)

分析:看数据大概能猜出dp有两个维度,一个是时间,一个是人数。

刚开始我打算设dp[i][j]为:经过t秒,第j个人成为队首的状态下 ,电梯人数的期望 。但是很显然这是个假的状态,(无法转移+遗漏了解空间) 。

正解:

dp[i][j]表示经过了i秒,电梯里有j个人的概率 。
最终答案就是Σdp[t][i]*i
边界:dp[i][0]=(1.0-p)^i
转移:dp[i][j]=dp[i-1[j]*(1.0-p) + dp[i-1][j-1]*p 
(dp[i-1][j]状态下不进人的概率*dp[i-1][j] + dp[i-1][j-1]状态下进人的概率*dp[i-1][j-1])
(值得注意的是,当j==n, dp[i-1][j]这个状态下电梯再进人的概率为0,所以要判断)
#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 2e3+10;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//n个人排队进电梯  每一秒队首有p概率进电梯  一次只能进一个人 且只能按排队顺序进  问t秒后电梯人数期望
//dp[i][j] 过了i秒 电梯上有j个人的概率
//dp[i][0]=(1-p)^i
//dp[i][j]=dp[i-1][j]*(1-p)+dp[i-1][j-1]*p
//Σdp[t][i]*i  就是期望
int n,t;
double p,dp[maxn][maxn];
double ans=0;
double pow(double t,int x)
{
    double ans=1.0;
    while(x)
    {
        if(x&1) ans*=t;
        t*=t;
        x>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d %lf %d",&n,&p,&t);
    for(int i=0;i<=t;i++)
    {
        dp[i][0]=pow(1.0-p,i);
    }
    for(int i=1;i<=t;i++)
    {
        for(int j=1;j<=n;j++)
        {
            //dp[i][j] 过了第i秒 电梯恰好有j个人
            if(j<n) 
                dp[i][j]=dp[i-1][j]*(1.0-p) + dp[i-1][j-1]*p;
            else //此时dp[i-1][j]状态已经满人了,电梯加人这一事件的概率变成了0, 1.0-p==1
                dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*p;
        }
    }
    for(int i=1;i<=n;i++) ans+=dp[t][i]*i;
    printf("%.6f\n",ans);
    return 0;
}

P1654 OSU!

期望/概率dp入门+题单_第2张图片

参考大佬博客CLICK IT!

#include
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e5+10;
const int mx = 40;
const int mod = 1e9+5;
const ll inf = 34359738370;
const int INF = 1e9+7;
//一个01串 每个位置有p[i]是1  1-p[i]是0  在这个串中连续的X个1可以贡献X^3 
//的分数,这x个1不能被其他连续的1所包含 (只有极长01串作贡献)
//问贡献期望 1<=n<=1e5

//假设在第i位增加1个1 增加前第i-1位所在1串长度为x
// 贡献增加(1+x)^3-x^3 == 3x^2+3x+1
//用l1[i] 维护一次项的期望 即:i所在位置的1串长度期望 l[i]=(l[i-1]+1)*p[i]
//l2[i]   维护二次项的期望 即:i所在位置的1串长度平方的期望 l2[i]=l2[i-1]+2*l1[i-1]+1

//ans[i]=ans[i-1]+p*(3*l2[i-1]+3*l1[i]+1)
//  前i-1答案 + 第i个数成为1 对答案的贡献
int n;
double p[maxn],l1[maxn],l2[maxn],ans[maxn];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lf",p+i);
    for(int i=1;i<=n;i++)
    {
        l1[i]=(l1[i-1]+1.0)*p[i];//第i个变成1  对l1[i]的影响
        l2[i]=(l2[i-1]+2*l1[i-1]+1.0)*p[i];//第i个变成1  对(l1[i])^2的影响
        ans[i]=ans[i-1]+p[i]*(3*l2[i-1]+3*l1[i-1]+1.0);
    }
    printf("%.1f\n",ans[n]);
    return 0;
}

你可能感兴趣的:(期望dp,算法,动态规划,c++)