Codeforces1209G2 Into Blocks (hard version)
Description
给你 , 表示序列长度, 表示操作次数。
我们需要达成这么一个目标状态:如果存在 这个元素,那么必须满足所有 元素都必须在序列中连续。
然后你可以进行这么一种操作,将所有的 元素的变为任意你指定的 元素,并且花费 的花费, 代表 元素的个数。
现在有 次询问,每次询问单点修改一个位置的值,求修改完之后最小花费使得序列满足目标状态。
注意:更新不是独立的,之前的更新会保留。
Solution
暴力做法就是将计算出来每个颜色 最早&晚 的出现位置 ,并将其视作一个区间
可以将 个元素分成若干连续段,每段内的颜色的出现的位置的 都在这个段内,或者说这样的划分是 “极长的”
另外一种刻画方式是将上述两个位置记成区间 表示该区间中的每个 的颜色和 的颜色相同
对每个颜色的 包含的 加一那么上面说的不交区间就是上个 权点以右,当前零权点(包含)以左
这时可以利用区间最小值来表示两个段的分界点:全局最小值一定存在取值点 ,因为其不会在右开的情况下被加一,而其它符合全局最小值的点必然是 权点
如果实现了将每段内的元素挂到段尾的 进行统计即可完成本题
将每种颜色的出现次数作为其第一次出现位置的权值 ,线段树上每个节点需要维护每个区间最小值靠左(包含自身)的最大的 ,区间最小值靠右(不包含自身)的最大 ,以及最小值中间段的和
使用 std::set
来维护每个颜色的所有出现次数,时间复杂度
Code
const int N=2e5+10;
int n,Q,a[N];
int sum[N<<2],Mx[N<<2],Mn[N<<2],tag[N<<2],lef[N<<2],rig[N<<2];
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
inline void push_tag(int p,int v){tag[p]+=v; Mn[p]+=v;}
inline void push_down(int p){
if(tag[p]){
push_tag(ls,tag[p]); push_tag(rs,tag[p]);
tag[p]=0;
} return ;
}
inline void push_up(int p){
Mx[p]=max(Mx[ls],Mx[rs]);
if(Mn[ls]<Mn[rs]){
Mn[p]=Mn[ls];
lef[p]=lef[ls]; rig[p]=max(Mx[rs],rig[ls]);
sum[p]=sum[ls];
}else if(Mn[rs]<Mn[ls]){
Mn[p]=Mn[rs];
lef[p]=max(lef[rs],Mx[ls]); rig[p]=rig[rs];
sum[p]=sum[rs];
}else{
Mn[p]=Mn[ls];
sum[p]=sum[ls]+sum[rs]+max(rig[ls],lef[rs]);
lef[p]=lef[ls]; rig[p]=rig[rs];
}
return ;
}
inline void Plus(int st,int ed,int d,int p=1,int l=1,int r=n){
if(st<=l&&r<=ed) return push_tag(p,d);
int mid=(l+r)>>1; push_down(p);
if(st<=mid) Plus(st,ed,d,lson);
if(ed>mid) Plus(st,ed,d,rson);
return push_up(p);
}
inline void Modify(int pos,int v,int p=1,int l=1,int r=n){
if(l==r) return lef[p]=Mx[p]=v,void();
int mid=(l+r)>>1; push_down(p);
if(pos<=mid) Modify(pos,v,lson);
else Modify(pos,v,rson);
return push_up(p);
}
#undef ls
#undef rs
#undef lson
#undef rson
set<int> app[N];
inline void ins(int c){
if(!app[c].size()) return ;
Modify(*app[c].begin(),app[c].size());
if(app[c].size()>1) Plus(*app[c].begin(),*prev(app[c].end())-1,1);
}
inline void erase(int c){
if(!app[c].size()) return ;
Modify(*app[c].begin(),0);
if(app[c].size()>1) Plus(*app[c].begin(),*prev(app[c].end())-1,-1);
}
signed main(){
n=read(); Q=read();
for(int i=1;i<=n;++i) app[a[i]=read()].insert(i);
for(int i=1;i<=200000;++i) ins(i);
print(n-sum[1]-lef[1]-rig[1]);
while(Q--){
int x=read(),y=read();
erase(a[x]);
app[a[x]].erase(x);
ins(a[x]);
erase(y);
app[y].insert(x);
ins(y);
a[x]=y;
print(n-sum[1]-lef[1]-rig[1]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律