[ABC379D] Home Garden
題意:
開始有一個空集,有 \(Q\) 次操作,每次有標識數 \(op\):
- 若 \(op\) 為 \(1\):為集合新增一個元素 \(0\)。
- 若 \(op\) 為 \(2\):輸入 \(T\),為集合內所有元素增加 \(T\)。
- 若 \(op\) 為 \(3\):輸入 \(H\),刪除集合內不小於 \(H\) 的元素,並輸出刪除元素個數。
資料範圍:\(1 \leq Q \leq 2 \times 10^{5}\),\(1 \leq T,H \leq 10^{9}\)
思路:
因為題中有多元素修改操作,所以我使用線段樹統計區間加來實現。
設變數 \(l\) 和 \(r\) 表示未被刪除的元素集合的線上段樹內的位置。
我們發現 \(1 \leq Q \leq 2 \times 10^{5}\),直接用線段樹根節點表示 \(1\) 到 \(2 \times 10^{5}\) 這個區間。
- 對於操作 \(1\):直接令 \(r\) 加一即可。
- 對於操作 \(2\):線段樹對區間 \(l\) 和 \(r\) 加 \(T\)。
- 對於操作 \(3\):我們發現未刪集合在在 \(l\) 到 \(r\) 內內單調不增,那麼考慮二分找出在 \(l\) 到 \(r\) 內第一個小於 \(H\) 的元素位置,設它為 \(L\),那麼答案就是 \(L-l\),輸出後將 \(l\) 賦值為 \(L\) 即可更新未刪集合。
程式碼:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct tree {
#define ls (k<<1)
#define rs ((k<<1)|1)
#define mid ((l+r)>>1)
ll add[800005];
void push_down(ll k) {
if(!add[k]) return;
add[ls]+=add[k];
add[rs]+=add[k];
add[k]=0;
}
void solve(ll k, ll l, ll r, ll L, ll R, ll op) {
if(L<=l&&r<=R) {
add[k]+=op;
return;
}
push_down(k);
if(L<=mid) solve(ls, l, mid, L, R, op);
if(R>mid) solve(rs, mid+1, r, L, R, op);
}
ll query(ll k, ll l, ll r, ll op) {
if(l==r) return add[k];
push_down(k);
if(op<=mid) return query(ls, l, mid, op);
else return query(rs, mid+1, r, op);
}
}sg;
int main() {
ll Q, T, H, l=1, r=0, op;
cin >> Q;
while(Q--) {
cin >> op;
if(op==1) r++;
else if(op==2) {
cin >> T;
if(l<=r) sg.solve(1, 1, 200000, l, r, T); //注意判斷是否是空集
} else if(op==3) {
cin >> H;
//使用二分找出l-r內第一個小於H的元素位置,若都不小於H那麼l將會更新為r+1合題意
ll L=l, R=r, Mid;
while(L<=R) {
Mid=(L+R)/2;
if(sg.query(1, 1, 200000, Mid)>=H) L=Mid+1;
else R=Mid-1;
}
cout << L-l << endl;
l=L; //更新未刪集合
}
}
return 0;
}