Red is good
事先说明,看的题解
题目描述
桌面上有R张红牌和B张黑牌,随机打乱顺序后放在桌面上,开始一张一张地翻牌,翻到红牌得到1美元,黑牌则付出1美元。可以随时停止翻牌,在最优策略下平均能得到多少钱。
输入格式
一行输入两个数R,B,其值在0到5000之间
输出格式
在最优策略下平均能得到多少钱。
样例
样例输入
5 1
样例输出
4.166666
数据范围与提示
输出答案时,小数点后第六位后的全部去掉,不要四舍五入.
…………
很显然啊,我不会
根据本题(和题解),可以发现,最优策略是指如果发现抽牌亏的概率平均下来特别大,那么就直接停止,例如你在知道剩下的都是黑牌,那么你肯定不会去抽,或者有一万张黑牌,有两张红牌,那么你也一定不会去冒这个险
根据绿豆蛙和osu,可以想到利用期望的线性性质从上一个状态转移到本状态来,一开始想了一个抽象的dp,以剩下i张牌的期望为f[i]:
\(f[i]=(f[i-1]+1)*p[i]-f[i-1]*(1-p[i])\)
截止条件就是红牌数为0
但很明显不对,首先是没有把红和黑分开(总不能一直抽红吧),其次是抽黑牌应该是\((f[i-1]-1)*(1-p[i])\),最后是我停止不一定就是没红牌了,根据上面的口胡会发现,如果我亏的概率大也会停止,然后就去看题解了
正题开始
既然我没法根据剩余的牌来推,那么就反着来,根据我现有的牌(假设我要抽的只是牌堆中的一部分牌)来建dp,令f[i][j]为我从i张红牌,j张黑牌抽牌的期望,那么转移方程就是:
\(f[i][j]=(f[i-1][j]+1)*p[i]+(f[i][j-1]-1)*(1-p[i])\)
\(p[i]=i/(i+j)\)
但这个方程没有考虑停止的情况,也就是说,他会一直抽,直到牌堆中没牌,那么我们就需要即时停止,对于这个里面的f[i][j],如果它停止,说明在这种情况下就根本不能抽(即期望值为负),因此考虑将它的值赋为0,即在这个状态下没抽
那么就有:
\(f[i][j]=max( 0, ( f[i-1][j]+1)*p[i]+(f[i][j-1]-1)*(1-p[i]) )\)
本来到这里就应该结束了,但很显然有个疑问,假设我f[i-1][j]的期望为负,理应来讲它被赋为了0,那不应该停止吗?为什么还会用于f[i][j]的计算?
实际上,这里的dp只是揭露了整个计算过程的一半,这里从计算到输出结果实际上是一个递归
也就是说,这里的DP实际上反映的是递归回来的过程,而并非真的计算转移(我认为),因此这个转移方程没有问题,0就是停止的状态
没了
#include<bits/stdc++.h>
using namespace std;
double f[5001][5001];
int n,m;
double p;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
f[i][0]=i;
for(int j=1;j<=m;j++){
p=(double)i/(i+j);
f[i][j]=max(0.0,((f[i-1][j]+1)*p+(f[i][j-1]-1)*(1.0-p)));
}
}
cout<<setprecision(6)<<fixed<<f[n][m]-0.0000005<<endl;
}
滚动数组
#include<bits/stdc++.h>
using namespace std;
double f[5001];
int n,m;
double p;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
f[0]=i;
for(int j=1;j<=m;j++){
p=(double)i/(i+j);
f[j]=max(0.0,((f[j]+1)*p+(f[j-1]-1)*(1.0-p)));
}
}
cout<<setprecision(6)<<fixed<<f[m]-0.0000005<<endl;
}