总结
关于期望,大多数题目都可采用动态规划或记忆化搜索解决。这种题目的难点就在于如何灵活地处理状态的定义于传递
一些题目
接下来分析一些做过的题目awa
1)红黑牌
你有一个包含R红色和B黑色牌的牌组。
你正在玩下面的游戏:你洗牌,然后开始逐个处理牌。对于你翻转的每张红牌,你得到一美元,而对于你翻转的每张黑卡,你必须支付1美元。在任何时候(包括比赛的开始),你都可以停下来并保留你的钱。
给你R和B,你最佳地玩这个游戏你将获得的金额的期望值。
输入格式
多组测试数据。
第一行,一个整数G,表示有G组测试数据。 1 <= G <= 10
每组测试数据格式:
第一行,两个整数,R和 B。 0 <=R,B <= 5000。
输出格式
共G行,共G行,每行一个实数,误差不超过0.0001。
样例
输入:
10
0 7
4 0
5 1
2 2
12 4
11 12
5000 5000
4950 4772
4446 4525
4446 4526
输出:
0.0
4.0
4.166666666666667
0.6666666666666666
8.324175824175823
1.075642825339958
36.90021846438633
191.15589434419024
8.13249058054577E-4
0.0
分析
这题的状态的设计与传递的大概构想很容易想出。
就dp[ i ][ j ]表示目前拿了i个红球,j个黑球的期望值,然后转移就从dp[ i - 1 ][ j ]或dp[ i ][ j - 1]来。
然而我在理解题意上就有偏差。题目讲到,在任何时候你都可以停下来保留你的钱,但没有说一定要在哪里保留。也就是说,整个过程没有期望值为负数的情况,最小为0,一旦出现哪种情况期望值为负数的,索性停止。
code
#include <bits/stdc++.h>
using namespace std;
int G,r,b;
double dp[5005][5005];
int main(){
scanf("%d",&G);
while(G--)
{
scanf("%d%d",&r,&b);
dp[0][0] = 0;
for(int i=1;i<=r;++i)dp[i][0]=i;
for(int i=1;i<=b;++i)dp[0][i]=0;
for(int i=1;i<=r;++i){
for(int j=1;j<=b;++j){
dp[i][j]=max( (dp[i-1][j] + 1) * i / (i+j)
+ (dp[i][j-1] - 1) * j / (i+j)
, 0.0);
}
}
printf("%lf\n",dp[r][b]);
}
return 0;
}
2)黑白球
一个箱子里面有n个黑球m个白球。你每小时都随机从箱子里面抽出两个小球,然后把这两个球都染成黑球,然后再放回去。问需要多少小时才能把所有小球变成黑色小球?输出期望值。
输入格式
第一行,一个整数G,表示有G组测试数据。1 <= G <= 10
每组测试数据格式如下:
一行,两个整数,n和m。1 <=n,m<=47。
输出格式
共G行,每行一个实数。误差不能超过0.00001。
样例
输入:
5
1 1
2 1
1 2
4 7
1 3
输出:
1.0
1.5
2.0
13.831068977298521
3.4
分析
直觉告诉我设状态设dp[ i ][ j ],但这道题中白球终究变为黑球,所以直接设dp[ i ]为有i个黑球时的期望次数。
这道题容易疏忽的地方:每次拿两个时,有黑黑、黑白、白白三种颜色搭配情况,但是注意,黑白搭配的概率是黑黑、白白的两倍!
code
#include<bits/stdc++.h>
using namespace std;
int G;
double dp[100];
int N,n,m;
int main(){
scanf("%d",&G);
while(G--){
scanf("%d%d",&n,&m);
double Tot=(n+m)*(n+m-1);
memset(dp,0,sizeof(dp));
for(int i=n+m-1;i>=n;--i){
int j=n+m-i;
double t1=(dp[i+2]+1.0)*double(j)*double(j-1)/Tot;
double t2=(dp[i+1]+1.0)*double(i)*double(j)/Tot;
double t3=double(i)*double(i-1)/Tot;
t2*=2.0;
dp[i]=(t1+t2+t3)/(1.0-t3);
}
printf("%lf\n",dp[n]);
}
return 0;
}
3)NOIP2016tg换教室
分析
这题把最短路和期望两个板块绑在了一块,主要难点集中在期望。
不难想出定义dp[ i ][ j ][ k(bool类型) ]为到了第i节课,用了j次申请,是在c教室还是d教室
于是便有了一大坨的转移代码,不多说了、
code
#include<bits/stdc++.h>
using namespace std;
int n,m,V,E;
double dp[2001][2001][2],k[2001];
int dis[301][301],c[2001],d[2001];
int main(){
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",&k[i]);
///
memset(dis,63,sizeof(dis));
while(E--){
int t1,t2,t3;
scanf("%d%d%d",&t1,&t2,&t3);
dis[t1][t2]=min(dis[t1][t2],t3);
dis[t2][t1]=min(dis[t2][t1],t3);
}
for(int l=1;l<=V;++l)
for(int i=1;i<=V;++i)
for(int j=1;j<=V;++j)
dis[i][j]=min(dis[i][j],dis[i][l]+dis[l][j]);
for(int i=1;i<=V;++i)dis[i][i]=0;//注意要加上这句话qwq
///
for(int i=0;i<=n;++i)
for(int j=0;j<=m;++j)
dp[i][j][0]=dp[i][j][1]=2100000000;
dp[1][0][0]=dp[1][1][1]=0;
for(int i=2;i<=n;++i){
int c1=dis[c[i-1]][c[i]],c2=dis[c[i-1]][d[i]];
int c3=dis[d[i-1]][c[i]],c4=dis[d[i-1]][d[i]];
for(int j=min(m,i);j;--j){
dp[i][j][1]=min(
dp[i-1][j-1][0]+c1*(1-k[i])+c2*k[i],
dp[i-1][j-1][1]+c1*(1-k[i-1])*(1-k[i])+c2*(1-k[i-1])*k[i]+
c3* k[i-1] *(1-k[i])+c4* k[i-1] *k[i]
);
dp[i][j][0]=min(
dp[i-1][j][0]+c1,
dp[i-1][j][1]+c1*(1-k[i-1])+c3*k[i-1]
);
}
dp[i][0][0]=dp[i-1][0][0]+c1;
}
double ans=dp[n][0][0];
for(int i=1;i<=m;++i){
ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
}
printf("%.2lf",ans);
return 0;
}