CF1784C Monsters (hard version) 题解 线段树

发布时间 2023-12-15 18:03:07作者: quanjun

题目链接:https://codeforces.com/problemset/problem/1784/C

题目大意:

你面前有 \(n\) 只怪兽,每只怪兽都有一个初始血量,你可以进行两类操作:

  • 操作1:选择任意一个血量大于 \(0\) 的怪兽,并将它的血量降低 \(1\)
  • 操作2:将所有存活的怪物的血量各自减去 \(1\),如果存在怪物的血量恰好降为 \(0\)(即这只怪物之前的血量为 \(1\),现在变为 \(0\) 了),则操作2不会结束,而是继续将所有存活的怪兽的血量再各自减去 \(1\),如果仍有怪物的血量恰好降为 \(0\),则继续将所有存活的怪兽的血量再各自减去 \(1\),……,如是操作,直至某次操作后不存在怪物的血量恰好降为 \(0\),或者所有的怪物都被消灭为止。

你有 \(n\) 个子问题需要单独讨论,第 \(i\) 个子问题你需要回答出对于前 \(i\) 值怪兽,将其全部消灭最少需要进行几次操作1。

解题思路:

对于任何一个前缀,最优解肯定是先进行若干次操作1,最后进行一次操作2整体清零。

并且进行操作2的时候,所有的数字升序之后肯定是连续的,假设进行到当前操作的时候有 \(m\) 个不同的数字。

那么对于每一个数字 \(x\),先将 \(x\) 加进去,如果加入 \(x\) 之后有 \(k_x\) 个数值 \(\le x\) 的有效数字,则:

  • 如果 \(k_x \le x\),则 \(x\) 肯定可以作为有效数字;
  • 如果 \(k_x \gt x\),则 \(x\) 必然无效

而且一旦 \(x\) 无效,对于所有的 \(y \ge x\),均要 \(k_y \leftarrow k_y + 1\)

但是如果加入 \(x\) 之后,存在一个 \(y\) 满足 \(k_y \gt y\)(因为 \(k_y\) 增加了 \(1\)),则 \(y\) 将由有效变为无效。

此时,有效的数字 \(x\) 需要加入,无效的数字 \(y\) 需要删除(即用 \(x\) 替代 \(y\)),即答案加上 \(x - y\)\(x\)\(y\) 可能是一样的)。

所以我们可以用线段树维护一个区间信息。

然后我们其实就需要判断 \(x - k_x \lt 0\) 是否有即可。

因为一开始所有 \(k_x = 0\),所以我们可以维护一个区间 \([1, n]\),一开始第 \(x\) 个点对应的数值为 \(x\)

每当碰到一个数 \(x\),就先将区间 \([x, n]\) 范围内每个数都减小 \(1\),然后判断区间里有没有点对应的数值 \(\lt 0\),如果存在某个点 \(y\) 的数值 \(\lt 0\),就说明 \(k_y \gt y\),就说明加进来的是一个无效的点,找到最小的 \(\lt 0\)\(y\),答案加上 \(x\) 减去 \(y\),因为增加了一个减去 \(y\) 的操作,所以区间 \([y, n]\) 范围内的每个数都要加上 \(1\)

否则,加入的是一个合法的点。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int T, n, a[maxn];

int tr[maxn<<2], lazy[maxn<<2];
void push_up(int rt) {
    tr[rt] = min(tr[rt<<1], tr[rt<<1|1]);
}
void push_down(int rt) {
    if (lazy[rt]) {
        tr[rt<<1] += lazy[rt];
        lazy[rt<<1] += lazy[rt];
        tr[rt<<1|1] += lazy[rt];
        lazy[rt<<1|1] += lazy[rt];
        lazy[rt] = 0;
    }
}
#define lson l, mid, rt<<1
#define rson mid+1, r, rt<<1|1
void build(int l, int r, int rt) {
    lazy[rt] = 0;
    if (l == r) {
        tr[rt] = l;
        return;
    }
    int mid = (l + r) / 2;
    build(lson), build(rson), push_up(rt);
}
void add(int L, int R, int x, int l, int r, int rt) {
    if (L <= l && r <= R) {
        tr[rt] += x;
        lazy[rt] += x;
        return;
    }
    push_down(rt);
    int mid = (l + r) / 2;
    if (L <= mid) add(L, R, x, lson);
    if (R > mid) add(L, R, x, rson);
    push_up(rt);
}
int query(int l, int r, int rt) {
    if (l == r) {
        return tr[rt] < 0 ? l : -1;
    }
    push_down(rt);
    int mid = (l + r) / 2;
    return (tr[rt<<1] < 0) ? query(lson) : query(rson);
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d", a+i);
        int m = 0;
        long long res = 0;
        build(1, n, 1);
        for (int i = 1; i <= n; i++) {
            add(a[i], n, -1, 1, n, 1);
            int x = query(1, n, 1);
            if (x == -1)
                m++, res += a[i] - m;
            else
                add(x, n, 1, 1, n, 1), res += a[i] - x;
            printf("%lld ", res);
        }
        puts("");
    }
    return 0;
}

注:本题还有一种使用 multiset 并且反着推的贪心做法,但是没看懂,可以参考一下 这份代码 ,等以后有机会再补充这个解法 但是 99% 会放鸽子

参考资料: