[题解]CF1209G2 Into Blocks (hard version)
思路
首先考虑 Easy Version 没有修改的做法。记 \(l_i,r_i\) 表示颜色 \(i\) 第一次和最后一次出现的位置,若想颜色 \(c\) 不变,必须使得 \([l_c,r_c]\) 区间所有不为 \(c\) 的点都要变,但是可以中间有一个点的颜色为 \(x\),满足 \(r_x > r_c\),那么此时 \((r_c,r_x]\) 这段区间中也必须变成 \(c\) 的颜色。这启发我们将序列分段,使得段中出现的所有颜色都仅在这个段中出现,那么每一段都可以保留一种颜色不动,因此保留段中众数即可。
考虑带修怎么做。一个经典的转化:对于每一个颜色 \(c\) 将 \([l_c,r_c]\) 打上一个标记,记 \(mk_i\) 表示 \((i,i + 1)\) 被标记的次数。那么两个相邻 \(mk\) 为 \(0\) 的位置就是一段。
维护分界点比较平凡,因为 \(\forall i \in [1,n],mk_i \geq 0\),因此维护 \(mk\) 的最小值,若一个区间中有分界点则最小值就是分界点。
维护每一段的众数,注意到每一段必定不存在在段外有与段内相同颜色的点,因此段内的众数就是在段内出现过的颜色所出现过的数量,不妨在 \(l_c\) 时记录 \(c\) 出现过的数量,记为 \(val\)。
其实我们可以直接把信息丢到线段树上。还需维护三个信息 \(sum,lval,rval\):
- \(sum\):表示在区间中完整段的贡献,对应在 \(mk\) 上就是被两个零包起来的段的答案。
- \(lval\):表示区间中左端点所在段中的权值最大值,对应在 \(mk\) 上就是区间左端点至区间最左零这段区间的权值最大值。\(rval\) 同理。
考虑如何在线段树上合并这些信息(将 \(ls,rs\) 合并为 \(u\)),假设 \(u\) 区间至少包含一个分界点:
- 若仅在 \(ls\) 区间中出现了分界点,即形如:
[xxx0xx][xxxxxx]。 - 若仅在 \(rs\) 区间中出现了分界点,即形如:
[xxxxxx][xx0xxx]。 - 若在两个区间中都出现了分界点,即形如:
[xxx0xx][xx0xxx]。
不难发现如果两个 \(u\) 区间没有分界点,则按照上述任意转移都是正确的。因为 \(mk_1\) 不一定为 \(0\),所以在线段树上 \([1,n]\) 的节点的 \(sum\) 信息中可能不包含 \(1\) 所在段的贡献,因此在 \(1\) 前面插入一个 \(mk\) 为 \(0\) 的哨兵即可。
Code
#include <bits/stdc++.h>
#define re register
using namespace std;
const int N = 2e5 + 10;
int n,q;
int arr[N];
set<int> col[N];
inline int read(){
int r = 0,w = 1;
char c = getchar();
while (c < '0' || c > '9'){
if (c == '-') w = -1;
c = getchar();
}
while (c >= '0' && c <= '9'){
r = (r << 3) + (r << 1) + (c ^ 48);
c = getchar();
}
return r * w;
}
struct{
#define ls(u) (u << 1)
#define rs(u) (u << 1 | 1)
#define val(u) (tr[u].val)
struct value{
int sum,val,lval,rval,Min;
value friend operator +(const value &a,const value &b){
value res;
res.val = max(a.val,b.val);
res.Min = min(a.Min,b.Min);
if (a.Min < b.Min){
res.sum = a.sum;
res.lval = a.lval; res.rval = max(a.rval,b.val);
}
else if (a.Min > b.Min){
res.sum = b.sum;
res.lval = max(a.val,b.lval); res.rval = b.rval;
}
else{
res.sum = a.sum + b.sum + max(a.rval,b.lval);
res.lval = a.lval; res.rval = b.rval;
}
return res;
}
};
struct node{
int l,r,tag;
value val;
}tr[N << 2];
inline void build(int u,int l,int r){
tr[u] = {l,r};
if (l == r) return;
int mid = l + r >> 1;
build(ls(u),l,mid); build(rs(u),mid + 1,r);
}
inline void calc(int u,int k){
val(u).Min += k; tr[u].tag += k;
}
inline void pushup(int u){
val(u) = val(ls(u)) + val(rs(u));
}
inline void pushdown(int u){
if (tr[u].tag){
calc(ls(u),tr[u].tag); calc(rs(u),tr[u].tag);
tr[u].tag = 0;
}
}
inline void update(int u,int x,int k){
if (tr[u].l == tr[u].r) return (val(u).val = val(u).lval = val(u).rval = k),void();
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) update(ls(u),x,k);
else update(rs(u),x,k);
pushup(u);
}
inline void modify(int u,int l,int r,int k){
if (l > r) return;
if (l <= tr[u].l && tr[u].r <= r) return calc(u,k);
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(ls(u),l,r,k);
if (r > mid) modify(rs(u),l,r,k);
pushup(u);
}
#undef ls
#undef rs
#undef val
}T;
inline void add(int x){
if (col[x].empty()) return;
int l = *col[x].begin(),r = *col[x].rbegin();
T.update(1,l,col[x].size());
T.modify(1,l,r - 1,1);
}
inline void del(int x){
if (col[x].empty()) return;
int l = *col[x].begin(),r = *col[x].rbegin();
T.update(1,l,0);
T.modify(1,l,r - 1,-1);
}
#define answer (n - T.tr[1].val.sum - 1)
int main(){
n = read() + 1,q = read();
for (re int i = 2;i <= n;i++) col[arr[i] = read()].insert(i);
T.build(1,1,n);
for (re int i = 0;i <= 2e5;i++) add(i);
printf("%d\n",answer);
while (q--){
int x,k;
x = read() + 1,k = read();
del(arr[x]); col[arr[x]].erase(x); add(arr[x]);
del(k); col[arr[x] = k].insert(x); add(k);
printf("%d\n",answer);
}
return 0;
}

浙公网安备 33010602011771号