洛谷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;
}
posted @ 2021-01-24 18:51  Hs-black  阅读(181)  评论(0编辑  收藏  举报