題解:[ABC379D] Home Garden

Anins發表於2024-11-10

[ABC379D] Home Garden

題意:

開始有一個空集,有 \(Q\) 次操作,每次有標識數 \(op\)

  1. \(op\)\(1\):為集合新增一個元素 \(0\)
  2. \(op\)\(2\):輸入 \(T\),為集合內所有元素增加 \(T\)
  3. \(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;
}

相關文章