Description
Time Limits: 3000 ms Memory Limits: 524288 KB
偶然间,chnlich 发现了他小时候玩过的一个游戏“魂斗罗”,于是决定怀旧。但是这是一个奇怪的魂斗罗 MOD。
有 N 个关卡,初始有 Q 条命。
每通过一个关卡,会得到 u 分和1条命,生命上限为 Q。其中 u=min(最近一次连续通过的关数,R)。
若没有通过这个关卡,将会失去1条命,并进入下一个关卡。
当没有生命或没有未挑战过的关卡时,游戏结束,得到的分数为每关得到的分数的总和。
由于 chnlich 好久不玩这个游戏了,每条命通过每个关卡的概率均为p(0<=p<=1),原先 chnlich 的最高分纪录是 S。
现在 chnlich 想要知道,当 p 至少为多少时,chnlich 期望获得的总分数能够超过原先的最高分。
Input
输入共一行,分别表示整数 N,整数 R,整数 Q,原先的最高分整数 S。
Output
输出共一行,若不存在这样的 p,输出"Impossible."(不包含引号),否则输出 p(保留6位小数)。
Sample Input
【样例输入一】
4 2 1 5
【样例输入二】
12 3 2 12
Sample Output
【样例输出一】
0.880606
【样例输出二】
0.687201
Data Constraint
Hint
【数据说明】
对于20%的数据,N<=15
对于50%的数据,N<=10000
对于100%的数据,N<=10^8,1<=R<=20,1<=Q<=5,保证 S 是一个可能出现的分数。
【补充说明】
例如,当 N=12,R=3,Q=2时
第一关:未通过 u=0 获得分数0 总分为0 剩余生命1
第二关:通过 获得分数1 总分为1 剩余生命2
第三关:通过 获得分数2 总分为3 剩余生命2
第四关:通过 获得分数3 总分为6 剩余生命2
第五关:通过 获得分数3 总分为9 剩余生命2
第六关:未通过 获得分数0 总分为9 剩余生命1
第七关:通过 获得分数1 总分为10 剩余生命2
第八关:未通过 获得分数0 总分为10 剩余生命1
第九关:未通过 获得分数0 总分为10 剩余生命0
游戏结束 总分为10
这是 chnlich 游戏的一种可能性
思路
鉴于题目限制及状态丰富多彩,不难看出这是一道恶心的二分答案+DP。
二分部分就是去二分出p值,每次check判断该p值的合法性。
考虑check部分:
先考虑50分写法,不妨设g[i][j][k]
表示在前i关连赢j局剩k条命的概率。
则显然有如下转移式:
g[i+1][min(R,j+1)][min(Q,k+1)]+=p*g[i][j][k];
g[i+1][0][k-1]+=(1-p)*g[i][j][k];
由于该游戏算分方式是在你死了或者你通关了的时候 加上累积分数。
则每一次转移,都可以对答案算分:Ret+=g[i][j][k]*p*min(j+1,R);
再考虑100分写法:
对于一个状态来说,它所更新的状态是一定的。
即对于每个状态,让它被更新的状态也是一定的。
显然,是一个矩乘加速。
最后,每次check的返回值就取决于Ret与S的大小关系。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXQ=10;
const int MAXR=25;
const int MAXK=105;
const int MAXN=10005;
const long long ONE=1;
const double E=0.000000000001;
int N,R,Q,S,Len,cct[MAXR][MAXQ];
struct node{
int n,m;
double a[MAXK][MAXK];
node operator *(const node &t)const {
if(m!=t.n)return t;
node rt;rt.n=n;rt.m=t.m;
memset(rt.a,0,sizeof(rt.a));
for(int i=0;i<=rt.n;i++)
for(int j=0;j<=rt.m;j++){
if(a[i][j]<E)continue;
for(int k=0;k<=m;k++){
if(t.a[j][k]<E)continue;
rt.a[i][k]+=a[i][j]*t.a[j][k];
}
}
return rt;
}
}M;
node quick_Pow(node x,int y){
node rt=x;y--;
while(y){
if(y%2)rt=(rt*x);
x=(x*x);y/=2;
}
return rt;
}
int check(double p){
/*memset(g,0,sizeof(g));
double Ret=0;g[0][0][Q]=1;
for(int i=0;i<N;i++)
for(int j=0;j<=R;j++)
for(int k=1;k<=Q;k++){
g[i+1][min(R,j+1)][min(Q,k+1)]+=p*g[i][j][k];
g[i+1][0][k-1]+=(1-p)*g[i][j][k];
Ret+=g[i][j][k]*p*min(j+1,R);
}
return Ret<=S;*/
memset(M.a,0,sizeof(M.a));
M.n=M.m=Len;M.a[Len][Len]=1;//从后往前.
for(int i=1;i<=R;i++)//赢后才得i分(不同于上面g数组下标),即先前连赢至少i-1局.
for(int j=1;j<=Q;j++){//剩余j条命.
M.a[cct[i][j]][cct[min(R,i+1)][min(Q,j+1)]]+=p;
if(j>1)M.a[cct[i][j]][cct[1][j-1]]+=1-p;//无命对答案无影响.
M.a[cct[i][j]][Len]+=p*i;
}
node Ans=quick_Pow(M,N);
return Ans.a[cct[1][Q]][Len]<=S;//初始状态.
}
void Prepare(){
for(int i=1;i<=R;i++)
for(int j=1;j<=Q;j++)
cct[i][j]=Len++;
}
int main(){
scanf("%d%d%d%d",&N,&R,&Q,&S);
Prepare();
if(N==0||check(1.0)){
printf("Impossible.\n");
return 0;
}
double l=0,r=1;
while(l+E<r){
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
printf("%.6f\n",r);
}