武汉理工大学第二届大学生程序设计竞赛 题解

这场题目感觉挺好的,因为我太菜了,所以写一下题解来加深自己的理解

k题 干员测试

题意 :有1到n的n个位置,每个位置上有一个数值 v a l val val,我们每次可以选择1到m的位置进行攻击,每次攻击每个位置上伤害为x,当有个位置上的值 v a l < = 0 val <= 0 val<=0时,则由后面位置的数补齐,请问需要最少多少次能使所有位置的值 v a l < = 0 ? val <= 0? val<=0?

错误思路:找到m个数的最小值,就算需要多少(假设为cnt次)能将它清0,然后将这m个数依次减去cnt * x, 那么就样总的时间复杂度就是 O ( n ∗ m ) O(n * m) O(nm), 妥妥的超时,我当时竟然还写了,还因为使用了一直没写过的vector的erase操作,debug了好久,交了好多发的 W A WA WA,,最后改对了然后T了就不知道怎么弄了

上述思路自然是正确的,是纯暴力的做法,那么我们考虑一下如何去优化?
主要分为以下两个步骤:
1.从m个数种找出一个最小值
2.对于m个数依次减去cnt * x

m个无序元素的元素最小值直接一看当然是直接遍历比较好,但是我们我们仔细想想,是将m个数删去几个数,然后加入几个数,那么找最小值用一个小根堆,不就是 O ( l o g m ) O(logm) O(logm)的时间复杂度吗?良心的c++为我们提供了priority_queue,就需要自己改改参数,就是一个小根堆了

我们我们考虑将m个数减去cnt * x,其实也就是将第一个数出队,剩下m - 1个数减去cnt * x(每次只出队一个数,重复元素不用管),那么不就是将区间[2, m]减去一个cnt * x呢,那么这你能不能想到线段树的懒标记呢,我们将这些数打上cnt * x的标记,因为每个数的下标是会变的,那么也就是说,我们使用一种不同于线段树的懒标记。

我们将所有元素都打上一个减标记,然后非队列中元素并没有这个标记,那么当一个元素入队时我们给他加上一个数值,抵消掉与他无关的减标记即可;

也就是说对于一个未入队的元素,我们记录一下在他入队之前给他了打了多少的减标记,再他入队的时候给他加上一个与减标记相等的数值即可(因为入队之前的减标记是与该元素无关的,只有入队之后的减标记才与该元素有关)

那么当一个元素出队时,我们要减其数值减去其减标记,然后才能计算cnt

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int n, m, x;
priority_queue<ll, vector<ll>, greater<ll> > q;
int a[100010];

void sol() {
	cin >> n >> m >> x;
	for (int i = 1; i <= n; i ++ ) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; i ++ ) {
		q.push(a[i]);
	} 
	ll ans = 0;
	int p = m;
	long long num = 0;
	while (q.size()) {
		int t = q.top(); q.pop();
		t -= num;
		int cnt = (t + x - 1) / x;
		ans += cnt;
		num += cnt * x;
		if (p <= n) q.push(a[++p] + num);
	}
	cout << ans << '\n';
}

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	sol();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值