codeforce 1430:F. Realistic Gameplay(思维 + 贪心 + dp)

在这里插入图片描述


题目大意:
你有一发可以装 k 发子弹的枪,你要击杀 n 波怪物,第 i i i 波怪物有 a i a_i ai 个,每只怪物一枪就死,但第 i i i 波怪物必须在 [ l i , r i ] [l_i,r_i] [li,ri] 的时间内全部击杀,发射子弹的时候不会消耗时间,但换子弹会消耗一秒,换弹匣的时候弹匣里的剩余子弹会全部丢弃。其中,对 i ∈ [ 1 , n ] i \in [1,n] i[1,n] 满足 r i ≥ l i ≥ r i − 1 r_i \geq l_i \geq r_{i-1} riliri1

问题是:初始时你的弹匣中装满k发子弹,杀光 n 波怪物,你至少需要花多少颗子弹?


问题同样可以转化成,杀光 n 波怪物,你至少需要浪费多少颗子弹,产生浪费的原因是某一波打完后你必须要换子弹,否则下一波无法在时间内全部击杀。

注意题目给出条件初始,如果在某一波击杀全部怪物后并换了子弹,对于下一波而言,你的子弹状态是和初始状态相同,且处理下一波意味着前面的怪物你已经全部处理完,这启发我们用 dp 的方法去解决这道题,因此我们构造状态 d p [ i ] dp[i] dp[i] 表示从第 i i i 波开始杀,至少需要浪费的子弹数量。

如果成功受到这一点的启发,那么就很容易想到对于每一个起点 i i i ,枚举 j j j [ i , j − 1 ] [i,j - 1] [i,j1] 波打完每一波都不换子弹,第 j j j 波打完换子弹,此时有转移方程 d p [ i ] = m i n ( d p [ i ] , d p [ j + 1 ] + l e f t ) dp[i] = min(dp[i],dp[j + 1] + left) dp[i]=min(dp[i],dp[j+1]+left) l e f t left left 表示换子弹前弹匣的剩余子弹,这部分子弹显然需要丢弃。

对于每一波,只要判断在给定剩余子弹 n u m num num 的情况下,打完这波的剩余时间和剩余子弹,判断在下一波到来之前有没有时间换子弹进行转移。如果某一波打完剩余时间是负数,那么在此之前一定要换一次子弹, j j j 没必要继续枚举下去。

这个过程需要倒过来 dp,其中有 d p [ n + 1 ] = 0 dp[n + 1] = 0 dp[n+1]=0,对其他值有 d p [ i ] = i n f dp[i] = inf dp[i]=inf,特殊情况是中间一直无法换子弹,但可以打完后面全部的怪物,这种情况 d p [ i ] = 0 dp[i] = 0 dp[i]=0,特判一下即可。

时间复杂度为 O ( n 2 ) O(n^2) O(n2)


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 10;
typedef long long ll;
const ll inf = 1e15;
#define pii pair<int,int>
#define fir first
#define sec second
int n, k;
ll dp[maxn], sum = 0;
struct node {
	int l, r, v;
	node() {}
	node(int li,int ri,int vi) {
		l = li;
		r = ri;
		v = vi;
	}
} a[maxn];
pii calc(int num,int p) {				//打完每一波的剩余子弹,和剩余时间,剩余时间小于 0 说明无解 
	int l = a[p].l, r = a[p].r, v = a[p].v;
	if (v <= num) return pii(num - v,r - l);
	else {
		int left = r - l - 1;
		v -= num;
		return pii((k - v % k) % k,left - (v / k - (v % k == 0)));
	}
}
int main() {
	scanf("%d%d",&n,&k);
	for (int i = 1; i <= n; i++)
		scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].v), sum += a[i].v;
	for (int i = 1; i <= n; i++)
		dp[i] = inf;
	dp[n + 1] = 0;
	for (int i = n; i >= 1; i--) {
		int num = k;
		for (int j = i; j <= n; j++) {
			pii tmp = calc(num,j);
			if (tmp.sec < 0) break;
			if (j == n) {
				dp[i] = 0;
				break;
			}
			if (tmp.sec + a[j + 1].l - a[j].r > 0) dp[i] = min(dp[i],dp[j + 1] + tmp.fir);
			num = tmp.fir;
		}
		//printf("****%d %lld\n",i,dp[i]);
	}
	//printf("***%d %d\n",calc(45,6).sec,calc(45,6).fir);
	printf("%lld\n",dp[1] == inf ? -1 : dp[1] + sum);
	
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值