Description
给定一个长度为 (n) 的序列 (a (a_i le n)) 。
定义一个删数操作为:
- 记当前序列长度为 (k) ,则删除数列中所有等于 (k) 的数。
如果能在有限次进行下列删数操作后将其删为空数列,则称这个数列可以删空。
给定 (m) 次修改操作:
- 单点修改
- 数列整体 (+1/-1)
求每次操作后的序列 (a) 来说,至少还需要修改几个数,才能将这个数删空?
Solution
子问题 1
我们先来看一个子问题,什么样的长度为 (len) 序列可以一下子删空?
首先答案和序列顺序无关,可以考虑把数打到值域上去,设数 (i) 出现的次数是 (cnt_i)。
不妨逆向思维一下,现在给你一个可以一下删空的元素,然后你还可以加入几个元素,让序列还是可以删空,这样样的数怎么加?
举个例子,比如当前序列是 ( ext{1 3 3}),很容易发现你可以加 (1) 个 (4),或者 (2) 个 (5),或者 (3) 个 (6) 等等...这样可以一开始删的时候把我们新增的玩意全部删除。总结一下规律,设序列长度是 (n),你可以加入 (k) 个 (n + k)。
这样再反过来想,完全删数的这个操作是无缝连接的,设序列当前长度为 (len),重复执行:
- (len gets len - cnt[len])
如果最终 (len) 可以变成 (0) 就是成功了 !
所以可以看作一个数轴,一个人初始在 (len) 点,然后 (cnt[len]) 表示从这个点出发,可以跳到 (len - cnt[len]),可以删完 (Leftrightarrow) 可以跳到 (0)。
这样的话,可以将 (cnt[i]) 看作一条线段 ([i - cnt[i] + 1, i]),(即从 (len) 出发的路线)。能够删完当且仅当路线无缝连接。由于 (sum cnt = len),而我们的目标从 (len) 走到 (0) 步长也是 (len),所以不会出现线段重叠也能删完的情况。
子问题 2
对于一个序列而言,至少还需要修改几个数,才能将这个数删空?
先给出结论:将 (1 le i le n) 的每条线段 ([i - cnt[i] + 1, i]) 打到数轴上(也可以理解为这个区间 (+1)),操作的最小次数就是值域 ([1, n]) 中,没有线段经过的点数(也可以理解为区间 ([1, n]) 中 (0) 的个数)。
必要性
考虑每次修改一个数最多只能使一条线段伸长一个长度(当然还会有一条线段缩短),所以最多只会减少一个 (0),所以 (ans le) (0) 的个数。
充分性
我们考虑构造一组解。
- 每次选出一个其线段左端点覆盖 (> 1) 次的数,或者不在 ([1, n]) 值域内的数,将其换成一个当前无线段覆盖的数,这样可以将一个 (0) 去掉。
然后就感性的证明了最小操作次数 (=) (0) 的个数。
如何支持修改?
如果朴素的用我们的结论是 (O(n^2m)) 的(每次修改都 (O(n^2)) 全部搞一遍)。
然后观察一下我们需要支持的操作:
- 区间即 ([1, n]) 求 (0) 的个数
- 区间修改(因为整体平移导致一些线段不能用,所以要动态删除/加入)
- 整体平移(即所有线段往右边/往左边平移一个单位)
- 单点修改(即线段延长缩短带来的效应)
因为整体平移,所以不会
如果没有整体平移,是个线段树。
既然我们不会整体平移的数据结构,但是我们知道区间求 (0) 的个数的这个区间是唯一的,既然线段不能平移,那么我们就平移查询的区间骂。维护一个 ([L, R]) 初始 (L = 1, R = n)。([L, R]) 表示当前 ([1, n]) 这个值域的区间在线段树下体现的编号是什么。
-
对于单点修改,发现只有两个线段变化了(分别是左端点缩短和伸长),直接修改即可,注意这个数在不在当前的值域 ([1, n]) 里,如果不在不能修改。
-
对于整体平移。令 (L gets L - x, R gets R -x)(可以理解为编号不动,如果线段往右边移动,那么查询区间就往左边移动,相反的情况的对称的)。然后注意加入新的集合线段/删除旧的线段(右端点不在 ([1, n]) 的线段)。
-
对于区间求 (0) 的个数。
佛了不会因为这题权值是非负的,所以记录最小值和最小值出现的次数就行。。。
时间复杂度
(O((N + M) log_2 N))
Tips
- 注意到 (L, R) 可能被移动到 (+/-m) 的情况,并且左端点最多伸出 (n) 的长度,所以线段树要开 (4) 倍,左右区间要开到 ([1, 2(n + m)])
- (cnt_i) 在平移意义下并不是 (i) 这个数字了,而是一个编号
- ([L, R]) 维护的是值域 ([1, n]) 的位置,所以对于新进来的一个数 (x),他应该在的编号是 (x + L - 1)。
Code
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 150005;
int n, m, tot, a[N], cnt[N * 4];
int tag[N * 16];
struct Node{
int v, cnt;
Node (){}
Node (int v, int cnt): v(v), cnt(cnt) {}
Node (Node a, Node b) {
v = min(a.v, b.v);
cnt = (v == a.v ? a.cnt : 0) + (v == b.v ? b.cnt : 0);
}
} dat[N * 16];
void inline pushup(int p) {
dat[p] = Node(dat[p << 1], dat[p << 1 | 1]);
}
void inline pushdown(int p) {
if (tag[p]) {
dat[p << 1].v += tag[p], dat[p << 1 | 1].v += tag[p];
tag[p << 1] += tag[p], tag[p << 1 | 1] += tag[p];
tag[p] = 0;
}
}
void build(int p, int l, int r) {
if (l == r) { dat[p] = Node(0, 1); return; }
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p);
}
void change(int p, int l, int r, int x, int y, int k) {
if (x > y) return;
if (x <= l && r <= y) {
dat[p].v += k, tag[p] += k;
return;
}
pushdown(p);
int mid = (l + r) >> 1;
if (x <= mid) change(p << 1, l, mid, x, y, k);
if (mid < y) change(p << 1 | 1, mid + 1, r, x, y, k);
pushup(p);
}
int query(int p, int l, int r, int x, int y) {
if (x <= l && r <= y) return dat[p].v == 0 ? dat[p].cnt : 0;
pushdown(p);
int mid = (l + r) >> 1, res = 0;
if (x <= mid) res += query(p << 1, l, mid, x, y);
if (mid < y) res += query(p << 1 | 1, mid + 1, r, x, y);
return res;
}
void inline add(int x, int k) {
change(1, 1, tot, x - cnt[x] + 1, x, k);
}
int main() {
scanf("%d%d", &n, &m);
tot = (n + m) * 2;
int l = n + m + 1, r = n + m + n;
for (int i = 1; i <= n; i++) scanf("%d", a + i), a[i] += l - 1, cnt[a[i]]++;
build(1, 1, tot);
for (int i = l; i <= r; i++) change(1, 1, tot, i - cnt[i] + 1, i, 1);
while (m--) {
int p, x; scanf("%d%d", &p, &x);
if (p) {
x += l - 1;
if (l <= a[p] && a[p] <= r) {
change(1, 1, tot, a[p] - cnt[a[p]] + 1, a[p] - cnt[a[p]] + 1, -1);
}
cnt[a[p]]--, a[p] = x, cnt[a[p]]++;
if (l <= a[p] && a[p] <= r) {
change(1, 1, tot, a[p] - cnt[a[p]] + 1, a[p] - cnt[a[p]] + 1, 1);
}
} else {
l -= x, r -= x;
if (x == 1) add(r + 1, -1), add(l, 1);
else add(l - 1, -1), add(r, 1);
}
printf("%d
", query(1, 1, tot, l, r));
}
return 0;
}