题目链接:https://codeforces.com/contest/1543/problem/C
题意:
给定三张牌,每次抽取一张牌,给定这三张牌被抽到的概率,分别记为 \(c,m,p\),并给定一个波动因子 \(v\),设当前抽到的这张牌被抽到的概率为 \(a\),若\(a \le v\),则将当前牌删除,将其概率 \(a\) 平分给还剩下的牌,若\(a > v\),则将当前牌的概率减少 \(v\),即当前牌的概率变为\(a-v\),并将减少的概率 \(v\) 分给其他牌。问抽到第三张牌的期望抽取次数(包含小数)是多少。
(\(t\)组测试样例(\(1 \le t \le 10\)),每次输入 \(c,m,p\) 和 \(v\) (\(c,m,p,v\)最多有四位小数)(\(0<c,m,p<1,c+m+p=1,0.1 \le v \le 0.9\)),输出期望抽取次数(要保证输出结果与答案的绝对误差或相对误差不超过 \({10^{-6}}\),即设输出结果为 a,答案为 b,则要保证\({ { {\frac {\vert {a-b} \vert} {max {(1,\vert {b} \vert)} } } } \le {10^{-6}} }\)))
思路:
概率 dp
设 \(dp[c][m][p]\) 为抽到第一张牌的概率为 \(c\),抽到第二张牌的概率为 \(m\),抽到第三张牌的概率为 \(p\) 时抽到第三张牌所需要抽取的期望次数。
我们已知 \(dp[0][0][1] = 1\),而对于其他任意状态 \(dp[c][m][p]\),从该状态可以抽取一、二、三中任意一张牌从而转移到下一个状态,若抽取的牌为第一或第二张,则根据其当前的概率是否大于 \(v\) 确定下一个状态,若抽取的牌为第三张,则直接结束,不需要再抽取。
即在当前状态 \(dp[c][m][p]\) 下可转移的状态可描述为:
\( 当前状态dp[c][m][p] = {\begin{cases} {c概率:抽取一张牌,这张牌为第一张牌,状态转移为抽取第一张牌之后的状态} \\ {m概率:抽取一张牌,这张牌为第二张牌,状态转移为抽取第二张牌之后的状态} \\ {p概率:抽取一张牌,这张牌为第三张牌,抽取结束} \end{cases}} \)
则可以这样计算当前状态的期望抽取次数:
当前状态的期望抽取次数\(dp[c][m][p]\) = (1+抽到第一张牌之后的状态的期望抽取次数) * c + (1+抽到第二张牌之后的状态的期望抽取次数) * m + (1+抽到第三张牌之后的状态的期望抽取次数(0)) * p
而由于转移到的下一个状态会受 c 和 m 取值的影响,因此我们还需要对 c 和 m 的大小分情况讨论(\(c>0,0<c\le{v},c=0\) 以及 \(m>0,0<m\le{v},m=0,3\times{3}=9\) 种情况,特别地,当 \(c=0,m=0\) 时,p=1,即此种情况即为已知的递归终点 \(dp[0][0][1]=1\))
因此转移式可表达为:
注意: 由于题目对精度有要求,因此我们需要先将 \(c,m,p,v\) 的值全部扩大精度的倒数倍,然后在搜索中每次需要乘以概率的时候将扩大的值还原为实际概率即可(互相比较和相互做减法时不需要还原为概率,直接使用扩大的值即可)
代码:
#include <iostream>
#include <iomanip>
using namespace std;
const double scale = 1e6;
double v;
double dfs(double c, double m, double p)
{ //由于概率之前扩大了,现在每次乘以概率的时候需要将扩大的值还原为实际概率,但是互相比较和相互做减法时不需要还原为概率,直接使用扩大的值即可
if (c > v)
{
if (m > v) //c>v,m>v
return dfs(c - v, m + v / 2, p + v / 2) * (c / scale) + dfs(c + v / 2, m - v, p + v / 2) * (m / scale) + 1;
else if (m > 0) //c>v,0<m<=v
return dfs(c - v, m + v / 2, p + v / 2) * (c / scale) + dfs(c + m / 2, 0, p + m / 2) * (m / scale) + 1;
else //c>v,m=0
return dfs(c - v, 0, p + v) * (c / scale) + 1;
}
else if (c > 0)
{
if (m > v) //0<c<=v,m>v
return dfs(0, m + c / 2, p + c / 2) * (c / scale) + dfs(c + v / 2, m - v, p + v / 2) * (m / scale) + 1;
else if (m > 0) //0<c<=v,0<m<=v
return dfs(0, m + c / 2, p + c / 2) * (c / scale) + dfs(c + m / 2, 0, p + m / 2) * (m / scale) + 1;
else ////0<c<=b,m=0
return dfs(0, 0, p + c) * (c / scale) + 1;
}
else //c=0
{
if (m > v) //c=0,m>v
return dfs(0, m - v, p + v) * (m / scale) + 1;
else if (m > 0) //c=0,0<m<=v
return dfs(0, 0, p + m) * (m / scale) + 1;
else //c=0,m=0
return 1;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (t--)
{
double c, m, p;
cin >> c >> m >> p >> v;
c *= scale, m *= scale, p *= scale, v *= scale; //同时扩大要求的精度的倒数倍,防止出现精度问题
cout << fixed << setprecision(12) << dfs(c, m, p) << endl;
}
}