【暖*墟】#动态规划# 期望DP的学习与练习
【一. 期望DP的初步介绍】
- 数学期望 P=Σ每一种状态*对应的概率。
- 大多数题 手动找公式 或者 DP推出 即可。
- 处理好边界,写好方程,代码超级简短。
【注意】与常规的求解不同,数学期望经常 逆向推出 。
常规的dp[x]可能表示 到了x这一状态有多少,最后答案是dp[n] 。
而数学期望的dp[x]一般表示 到了x这一状态还差多少,最后答案是dp[0] 。
【二. 期望DP的简单习题】
(1)Uva12230 Crossing Rivers
题意:
有个人每天要去公司上班,每次会经过N条河,家和公司的距离为D,默认在陆地的速度为1,
给出N条河的信息,包括起始坐标p,宽度L,以及船的速度v。船会往返在河的两岸,人到达河岸时,
船的位置是随机的(往返中)。问说人达到公司所需要的期望时间。
思路:
1,过每条河最坏的情况是t=3*L/v; 即去的时候船刚刚走。
2,过每条河最优的情况是t=L/v; 即去的时候船刚刚来。
3,由于船是均匀发布的,符合线性性质,所以平均下来,过每条河的时间t=2*L/v。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <stack> #include <queue> using namespace std; typedef long long ll; typedef unsigned long long ull; /*【uva12230】 Crossing Rivers 上班每次会经过N条河,家和公司的距离为D。在陆地的速度为1。 给出N条河的信息,包括起始坐标p,宽度L,以及船的速度v。 人到达河岸时,船的位置是随机的。问人达到公司所需要的期望时间。 */ /*【超级神奇啊】过河最坏的情况是t=3*L/v,最优的情况是t=L/v。 由于船是均匀发布的,符合线性性质,所以平均下来,过每条河的时间t=2*L/v。*/ void reads(int &x){ int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s-'0');s=getchar();} x=x*fx;//正负号 } int main(){ int n,d,p,l,v,kase=0; while(scanf("%d%d",&n,&d)&&d){ double ans=0; for(int i=1;i<=n;i++){ reads(p),reads(l),reads(v); d-=l; ans+=2.0*l/v; //记得陆地距离要减河宽 } printf("Case %d: %.3lf\n\n",++kase,(double)ans+d); } }
(2)SPOJ1026 Favorite Dice【赠券收集问题】
题意: 甩一个n面的骰子,问每一面都被甩到的次数期望是多少。
思路: 初始化 dp[n]=0; dp[i]=i/n*dp[i]+(n-i)/n*dp[i+1]+1; 化简逆推即可。答案为dp[0]。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <stack> #include <queue> using namespace std; typedef long long ll; typedef unsigned long long ull; /*【sp1026】Favorite Dice 一个n面的骰子,求期望掷几次能使得每一面都被掷到。*/ /*【赠券收集问题】已有i种不同的数,从集合中任选一个数得到新数的概率为n−i+1/n。 (结论)那么期望为:1/p=n/n−i+1。所以总期望为∑(i=1~n)n/n−i+1=∑(i=1~n)n/i。 ----> 设f[i]表示取了i种时,还需要取的数的期望次数。显然f[n]=0,逆推,答案为f[0]。 又:由于选第i个数后再选一个数 与已经选过的数不同 的概率为n−i/n,相同为i/n。 可得:f[i]=(n−i)/n*f[i+1]+i/n*f[i]+1; 化简:f[i]=f[i+1]+n/(n−i)。f[0]=∑(i=1~n)n/i。*/ // 其实最后答案就变成了调和级数:∑(i=1~n)n/i。 void reads(int &x){ int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s-'0');s=getchar();} x=x*fx;//正负号 } int main() { int T,n; double ans; reads(T); while(T--){ ans=1,reads(n); for(int i=1;i<n;i++) ans+=1.0*n/(n-i); printf("%.2lf\n",ans); } }
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <stack> #include <queue> using namespace std; typedef long long ll; typedef unsigned long long ull; /*【uva10288】优惠券 // 期望dp赠券收集问题 + 分数处理 每张彩票上面都有一种图案,共有n种,问在平均情况下最少需要买多少张彩票才能集齐n种。*/ /*【赠券收集问题】已有i种不同的数,从集合中任选一个数得到新数的概率为n−i+1/n。 (结论)那么期望为:1/p=n/n−i+1。所以总期望为∑(i=1~n)n/n−i+1=∑(i=1~n)n/i。 ----> 设f[i]表示取了i种时,还需要取的数的期望次数。显然f[n]=0,逆推,答案为f[0]。 又:由于选第i个数后再选一个数 与已经选过的数不同 的概率为n−i/n,相同为i/n。 可得:f[i]=(n−i)/n*f[i+1]+i/n*f[i]+1; 化简:f[i]=f[i+1]+n/(n−i)。f[0]=∑(i=1~n)n/i。*/ // 其实最后答案就变成了调和级数:∑(i=1~n)n/i。 const int N=59; ll lef[N+1],up[N+1],down[N+1]; void pre_cal(){ memset(lef,0,sizeof(lef)); memset(up,0,sizeof(up)); memset(down,0,sizeof(down)); lef[1]=1; for(int i=2;i<=N;i++){ ll d1=1,d2=1; //d1为分子,d2为分母 for(int j=2;j<=i;j++){ d1=d1*j+d2; d2*=j; //先计算所有的答案 ll p=__gcd(d1, d2); d1/=p; d2/=p; } d1*=i; ll p=__gcd(d1, d2); d1/=p; d2/=p; //约分 lef[i]=d1/d2,up[i]=d1%d2,down[i]=d2; } } int main(){ pre_cal(); int n; while(~scanf("%d",&n)){ if(up[n]==0){ printf("%lld\n",lef[n]); continue; } for(ll i=lef[n];i>0;i/=10) printf(" "); printf(" %lld\n",up[n]); //分子 printf("%lld ",lef[n]); //左部 for(ll i=down[n];i>0;i/=10) printf("-"); printf("\n"); for(ll i=lef[n];i>0;i/=10) printf(" "); printf(" %lld\n",down[n]); //分母 } }
(3)SGU495 Kids and Prizes
题意: 有n个奖品,m个人排队来选礼物,每个人打开的盒子,可能有礼物,也有可能已经被之前的人取走了。
打开之后,拿走礼物,把盒子放回原处。求最后m个人取走礼物的期望。
思路:
dp[1]=1; 后面的人 dp[i] = P(取到礼物盒子) + dp[i-1] = (n-dp[i-1])/n + dp[i-1]; 当然,也可以化简为公式,直接 printf("%.10lf\n",n*1.0*(1-pow((n-1)*1.0/n,m))) 。
——时间划过风的轨迹,那个少年,还在等你