【JZOJ】2867. Contra

本文探讨了一个基于魂斗罗游戏的数学问题,玩家需要在有限的命和关卡中,找到最优的概率策略以期望获得比历史记录更高的分数。文章采用二分查找和动态规划的方法,解决了一个关于游戏策略的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);
}
资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值