【线性DP】DTOJ 5022 游戏

该博客讨论了一种游戏策略问题,其中玩家需要在有限的资源下决定何时解决每个关卡的BOSS以达到最低代价。通过引理证明和动态规划的方法,博主展示了如何在进入下一关之前解决BOSS以优化路径。代码实现中展示了如何建立状态转移方程并求解最优化解。

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

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


Solution

先证明一个引理:

对于前 iii 关,若被迫传送(没打死BOSS),则必然会在进入 i+2i + 2i+2 关之前(第 i+1i +1i+1 关结束或者被传送)打死第 iii 关的BOSS。

首先,如果我们一直向后传送,到了非结尾的位置 j (j>i , j≠n)j \ (j >i\ ,\ j \neq n)j (j>i , j=n) ,返回解决 iii 的BOSS,这样 [i,j][i,j][i,j] 的路程就要折返两趟,结果肯定不如在进入在进入 i+2i + 2i+2 关之前解决更优。

其次,如果存在另一种方案,在每一关都使用技能1,被迫传送,到了最后一关在返回解决所有BOSS,如下图,可以看出这种方案可以等价转换为第二种方案:

在这里插入图片描述
所以引理成立。

因此我们可以考虑 dpdpdp

观察上面的2方案,可以发现我们其实是将关卡两两考虑,每个关卡可以和前后两个关卡分别配对,对于每两个关卡,我们有如下4种方案:

  • 解决1的BOSS,解决2的BOSS
  • 打掉1的BOSS一条血,解决2的BOSS,再回去解决1
  • 打掉1的BOSS一条血,打掉2的BOSS一条血,回去解决1,再回来解决2
  • 也可以不解决2的BOSS,只打掉一条血,但1的BOSS必须解决(根据引理),和下一关配对或传送回上一关再回来,后者只对于在最后一关的时候

所以,设 f[i][0/1]f[i][0/1]f[i][0/1] 表示在第 iii 关,BOSS已解决/未解决时的最小代价,则根据上面四种情况可以列出转移方程,get(i,0/1)get(i,0/1)get(i,0/1) 表示对于 iii 的BOSS,解决掉/只打掉一条血所需的代价:

f[i][0]=min⁡{f[i−1][0]+d+get(i,0)f[i−1][1]+d+get(i,0)+d+r1+[i≠n]∗df[i−1][0]+d+get(i,1)+2∗(d+r1)f[i][0] = \min\begin{cases}f[i-1][0]+d+get(i,0)\\ f[i - 1][1]+d+get(i,0)+d+r_1+[i\neq n]*d\\ f[i-1][0]+d+get(i,1)+2*(d+r_1)\end{cases}f[i][0]=minf[i1][0]+d+get(i,0)f[i1][1]+d+get(i,0)+d+r1+[i=n]df[i1][0]+d+get(i,1)+2(d+r1)
f[i][1]=f[i−1][0]+d+get(i,1)f[i][1] = f[i-1][0]+d+get(i,1)f[i][1]=f[i1][0]+d+get(i,1)


Code

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define int long long
using namespace std;

const int N = 1e6 + 5;

int n, r1, r2, r3, d;
int a[N], f[N][2];

int get(int i, bool op)
{
	if(op == 0) return a[i] * r1 + r3;
	return min(r2, a[i] * r1 + r1);
}

signed main()
{
	scanf("%lld%lld%lld%lld%lld", &n, &r1, &r2, &r3, &d);
	for(register int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
	f[1][0] = get(1, 0);
	f[1][1] = get(1, 1);
	for(register int i = 2; i <= n; i ++ )
	{
		int p = f[i - 1][1] + d + get(i, 0) + d + r1 + (i == n ? 0 : d);
		int q = f[i - 1][1] + d + get(i, 1) + 2 * (d + r1) ;
		f[i][0] = min(f[i - 1][0] + get(i, 0) + d, min(p, q));
		f[i][1] = f[i - 1][0] + get(i, 1) + d;
	}
	printf("%lld\n", min(f[n][0], f[n][1] + 2 * d + r1));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值