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