洛谷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;
}