CF1784C Monsters (hard version) 題解 線段樹

題目鏈接: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% 會放鴿子

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章