codeforces gym 103049G Great Expectations

题意:

一个游戏如果不出意外会花 $n(<=5000)$个时间完成,世界纪录是$r(<=5000)$,有$m(<=50)$种可能发生的意外情况浪费时间。

意外情况格式如下:

$t(<n)$  $p(0<p<1)$  $d$($<=1000$) 三个数,表示该事件发生在正常流程下的第$t$秒时,发生概率为$p$,浪费$d$的时间。

你可以随时重新开始游戏,问从一开始玩到成功打破记录(一局游戏从开始到结束时间小于$r$)期望一共花多少时间。

首先按照期望类题的套路,倒推状态转移,我们设$F[i][j]$为进行到正常流程下第$i$秒,实际本次尝试已经花了$j$秒还期望花多少时间突破记录。显然,答案就是$F[0][0]$

下面就是写转移方程,由于意外事件很少,我们将$F[i][j]$的意义改为已经进行到了第$i$个意外时间刚开始的时间,本次尝试已经花费了$j$秒且不知道这次事件是否会发生的期望剩余时间。得到如下转移方程:

$F[i][j]=(1-p[i])*(F[i+1][j+t[i+1]-t[i]]+t[i+1]-t[i])+p[i]*min(F[0][0],F[i+1][j+d[i]+t[i+1]-t[i]]+d[i]+t[i+1]-t[i]) (j+d[i]+n-t[i]<r)$

$F[i][j]=(1-p[i])*(F[i+1][j+t[i+1]-t[i]]+t[i+1]-t[i])+p[i]*F[0][0]   (j+d[i]+n-t[i]>=r)$

但是我们发现我们的转移方程出现了$F[0][0]$,而$F[0][0]$又是我们的最终答案,这就陷入了一个死环。当时训练的时候想到这一步就懵了,以为最后是要解方程,但是$min$又限制了我们无法通过解方程来得到答案。


看了题解之后才顿悟,既然$F[0][0]$未知,我们能不能尝试去二分$F[0][0]$,转移是用二分值,之后通过比较它与算出的$F[0][0]$的大小确定该调大还是调小。如果二分值大于$F[0][0]$,就意味着在一些决策点我们本应该选择重来但是我们选择了继续打,所以应当将二分值调小;如果小于$F[0][0]$,就意味着一些本应该继续打的地方我们选择了重来,所以应当将二分值调大。

 1 #include<iostream>
 2 #include<cstdlib>
 3 #include<cstdio>
 4 #include<cstring> 
 5 #include<cmath>
 6 #include<algorithm>
 7 #define N 5005
 8 #define double long double
 9 using namespace std;
10 int n,r,m;
11 int T[N];
12 double P[N];
13 int D[N];;
14 double F[55][N];
15 bool check(double X)
16 {
17     for(int i=m;i>=0;i--)
18     {
19         for(int j=T[i];j<r-(n-T[i]);j++)
20         {
21             F[i][j]=P[i]*(T[i+1]-T[i]+F[i+1][j+T[i+1]-T[i]]);
22             if(j+D[i]+(n-T[i])<r)
23             {
24                 F[i][j]+=(1.0-P[i])*min(X,D[i]+T[i+1]-T[i]+F[i+1][D[i]+T[i+1]-T[i]+j]);
25             }
26             else
27             {
28                 F[i][j]+=(1.0-P[i])*X;
29             }
30         }
31     }
32     return X>F[0][0];
33 }
34 int main()
35 {
36     scanf("%d%d%d",&n,&r,&m);
37     for(int i=1;i<=m;i++)
38     {
39         scanf("%d%Lf%d",&T[i],&P[i],&D[i]);
40     }
41     T[m+1]=n;
42     P[0]=1;
43     double li=0,ri=1e17,mid,ans;
44     while(fabs(ri-li)>=1e-8)
45     {
46         mid=(li+ri)/2.0;
47         if(check(mid)) ri=mid-(1e-8),ans=mid;
48         else li=mid+(1e-8);
49     }
50     printf("%.10Lf\n",ans);
51     return 0;
52 }
53  
View Code

 

posted @ 2021-05-24 21:19  Hzoi_joker  阅读(35)  评论(0编辑  收藏  举报