洛谷P5324 [BJOI2019]刪數(線段樹)

洛谷P5324 [BJOI2019]刪數(線段樹)

題目描述

對於任意一個數列,如果能在有限次進行下列刪數操作後將其刪爲空數列,則稱這個數列可以刪空。一次刪數操作定義如下:
>記當前數列長度爲 \(k\) ,則刪掉數列中所有等於 \(k\) 的數。

現有一個長度爲 \(n\) 的數列 \(a\),有 \(m\) 次修改操作,第 \(i\) 次修改後你要回答:
經過 \(i\) 次修改後的數列 \(a\),至少還需要修改幾個數纔可刪空?

每次修改操作爲單點修改或數列整體加一或數列整體減一。

數據範圍

\(1 \le n \le 150000\)

解題思路

這題大大好評,竟然是 AGC017C 原題(這樣再次增大了我和 Azusa 的差距,Azusa 做過所有的 AGC 題!)

作爲一道 AGC 題,你首先要進行轉化。我們考慮什麼樣的數列是不需要修改的,例如:\(3,3,3,4,6,6,7,10,10,10\)

發現了嗎,對於一個出現過 \(t\) 次的權值爲 c (\(c \le n\))的數,我們把它轉化爲整數數軸上的一段線段 \([t-c+1,c]\),如果線段正好鋪滿 \([1,n]\),那麼就是不需要修改的!

考慮修改的最少次數是多少?有一個很好的結論:沒有被覆蓋的點的個數就是答案!

有了這條性質,我們隨便線段樹做就行了,下面我們來證一下這個性質:

首先沒有被覆蓋的點的個數是答案的下界,這點顯然。

然後我們考慮如何構造出一種方案,我們只需縮短一些線段,並使之前被覆蓋的點被覆蓋恰好 1 次即可。

考慮從左掃到右,小於 1 的部分直接縮起來即可。對於當前點 x,假設 < x 的點都已經處理好了,且 x 被覆蓋了大於 1 次,我們找到覆蓋 x 的最小的右端點,保留,剩下的縮小一個單位即可。

#define ls p << 1
#define rs ls | 1
const int S = 150000, lim = S * 3;
const int N = S << 2;
int mn[N<<2], mnc[N<<2], add[N<<2], cnt[N], a[N], ans, st, m, n;
void Tag(int p, int c) { add[p] += c, mn[p] += c; }
void spread(int p) { Tag(ls, add[p]), Tag(rs, add[p]), add[p] = 0; }
void change(int p, int l, int r, int L, int R, int c) {
	if (L <= l && r <= R) return Tag(p, c);
	int mid = (l + r) >> 1; spread(p);
	if (L <= mid) change(ls, l, mid, L, R, c);
	if (R > mid) change(rs, mid + 1, r, L, R, c);
	mn[p] = min(mn[ls], mn[rs]);
	mnc[p] = (mn[ls] == mn[p] ? mnc[ls] : 0) + (mn[rs] == mn[p] ? mnc[rs] : 0);
}
void build(int p, int l, int r) { 
	mnc[p] = r - l + 1; if (l == r) return; 
	int mid = (l + r) >> 1; 
	build(ls, l, mid), build(rs, mid + 1, r); 
}
void query(int p, int l, int r, int L, int R) {
	if (L <= l && r <= R) return ans += !mn[p] ? mnc[p] : 0, void();
	int mid = (l + r) >> 1; spread(p);
	if (L <= mid) query(ls, l, mid, L, R);
	if (R > mid) query(rs, mid + 1, r, L, R);
}
int main() {
	read(n), read(m), st = S;
	for (int i = 1;i <= n; ++i) read(a[i]), ++cnt[a[i] += S];
	build(1, 1, lim);
	for (int i = 1;i <= n; ++i)
		if (cnt[st + i]) change(1, 1, lim, st + i - cnt[st + i] + 1, st + i, 1);
	for (int i = 1, op, x;i <= m; ++i) {
		read(op), read(x);
		if (op == 0) {
			if (x == 1) { if (cnt[st + n]) change(1, 1, lim, st + n - cnt[st + n] + 1, st + n, -1); --st; }
			else { ++st; if (cnt[st + n]) change(1, 1, lim, st + n - cnt[st + n] + 1, st + n, 1); }
		}
		else {
			int t = a[op];
			if (t <= st + n) change(1, 1, lim, t - cnt[t] + 1, t, -1);
			if (--cnt[t] && t <= st + n) change(1, 1, lim, t - cnt[t] + 1, t, 1);
			t = a[op] = x + st;
			if (cnt[t]) change(1, 1, lim, t - cnt[t] + 1, t, -1);
			cnt[t]++, change(1, 1, lim, t - cnt[t] + 1, t, 1);
		}
		ans = 0, query(1, 1, lim, st + 1, st + n), write(ans);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章