[题解]CF1209G2 Into Blocks (hard version)
思路
首先考虑 Easy Version 没有修改的做法。记 表示颜色 第一次和最后一次出现的位置,若想颜色 不变,必须使得 区间所有不为 的点都要变,但是可以中间有一个点的颜色为 ,满足 ,那么此时 这段区间中也必须变成 的颜色。这启发我们将序列分段,使得段中出现的所有颜色都仅在这个段中出现,那么每一段都可以保留一种颜色不动,因此保留段中众数即可。
考虑带修怎么做。一个经典的转化:对于每一个颜色 将 打上一个标记,记 表示 被标记的次数。那么两个相邻 为 的位置就是一段。
维护分界点比较平凡,因为 ,因此维护 的最小值,若一个区间中有分界点则最小值就是分界点。
维护每一段的众数,注意到每一段必定不存在在段外有与段内相同颜色的点,因此段内的众数就是在段内出现过的颜色所出现过的数量,不妨在 时记录 出现过的数量,记为 。
其实我们可以直接把信息丢到线段树上。还需维护三个信息 :
- :表示在区间中完整段的贡献,对应在 上就是被两个零包起来的段的答案。
- :表示区间中左端点所在段中的权值最大值,对应在 上就是区间左端点至区间最左零这段区间的权值最大值。 同理。
考虑如何在线段树上合并这些信息(将 合并为 ),假设 区间至少包含一个分界点:
- 若仅在 区间中出现了分界点,即形如:
[xxx0xx][xxxxxx]
。 - 若仅在 区间中出现了分界点,即形如:
[xxxxxx][xx0xxx]
。 - 若在两个区间中都出现了分界点,即形如:
[xxx0xx][xx0xxx]
。
不难发现如果两个 区间没有分界点,则按照上述任意转移都是正确的。因为 不一定为 ,所以在线段树上 的节点的 信息中可能不包含 所在段的贡献,因此在 前面插入一个 为 的哨兵即可。
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;
}
作者:WaterSun
出处:https://www.cnblogs.com/WaterSun/p/18560817
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)