Monsters And Spells(dp/rmq)

题目
题意:有n只怪兽,每只怪兽在 k i k_i ki时间点会出现,且生命值为 h i h_i hi。现在我们需要消灭这 n n n只怪兽。由于我们每次打气功,都需要从1开始累计,比如上一秒是打的气功是x,那么这一秒可以打x+1,或者选择重新开始,气功为1。如果上一秒没有打气功,则这一秒只能打1。要消灭这 n n n只小怪兽,我们总共需要消耗的最小气功是多少。

思路:
先根据每只小怪兽的生命值,计算消灭这只小怪兽,需要从哪里开始打气功 p o s i pos_i posi,比如 k i = 6 , h i = 3 k_i=6,h_i=3 ki=6,hi=3,说明我们需要在时间点6-3+1=4的位置开始打气功。如果要批量处理连续区间内的小怪兽,我们需要计算区间 [ l , r ] [l,r] [l,r]的最小打气功的开始位置 p p p,然后计算从 p p p k r k_r kr的气功总和即可。

由于本题的n比较小,可以支持 n 2 n^2 n2的复杂度。我们可以用dp。令 d p i dp_i dpi表示从i位置结束需要的最小气功。那么 d p i = d p j − 1 + d e a l ( j , i ) , ( 0 < j < = i ) dp_i = dp_{j-1} + deal(j,i),(0<j<=i) dpi=dpj1+deal(j,i),(0<j<=i)

计算区间最小值,可以用rmq算法。
注意,如果当前区间 [ j , i ] [j,i] [j,i]的最小开始下标 p p p,小于上一个区间的结束下标位置 k j − 1 k_{j-1} kj1,则说明区间 [ j , i ] [j,i] [j,i]的打气功位置和前面的区间发生了交叉,这是不可取的。

#include<bits/stdc++.h> 
using namespace std;
const int maxn = 110;
#define ll long long

int n;
ll dp[maxn];
int h[maxn], k[maxn];
int pos[maxn];// 下标i需要的开始位置pos[i] 

ll cal(ll x) {
	return x * (x + 1) / 2;
}

// rmq求pos数组区间 最小值 
int f[maxn][22];
void init_rmq() {
	for (int i = 0; i < n; ++i) {
		f[i][0] = pos[i];
	}
	for (int j = 1; (1<<j) <= n; ++j) {
		for (int i = 0; i + (1<<j) - 1 < n; ++i) {
			f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]);
		}
	}
}
int rmq(int l, int r) {
	int x = log2(r - l + 1);
	return min(f[l][x], f[r-(1<<x)+1][x]);
}

void solve() {
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &k[i]);
	}
	for (int i = 0; i < n; ++i) {
		scanf("%d", &h[i]);
	}
	for (int i = 0; i < n; ++i) {
		pos[i] = k[i] - h[i] + 1;
//		printf("%d ", pos[i]);
	}
//	printf("--\n");
	
	init_rmq();
	dp[0] = cal(h[0]);
//	printf("%d", dp[0]);
	for (int i = 1; i < n; ++i) {
		int p = k[i] - rmq(0, i) + 1;
		dp[i] = cal(p);
		for (int j = 1; j <= i; ++j) {
			// 起始下标超过了上个区间的结束位置,说明有交叉,不可取
			if (rmq(j, i) <= k[j-1]) { 
				continue;
			}
			p = k[i] - rmq(j, i) + 1;
			
			dp[i] = min(dp[i], dp[j-1] + cal(p));
		}
//		printf(" %d ", dp[i]);
	}
//	printf("---\n");
	
	printf("%lld\n", dp[n-1]);
}

int main() {
	int t;
	scanf("%d", &t);
//	int cas = 1;
	while (t--) {
//		printf ("case %d\n", cas++);
		solve();
	}
} 
/*

9
3
1 2 4
1 2 3

3
1 2 3
1 1 2
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值