这场题目感觉挺好的,因为我太菜了,所以写一下题解来加深自己的理解
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(n∗m), 妥妥的超时,我当时竟然还写了,还因为使用了一直没写过的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;
}