P6717 [CCO2018] Boring Lectures题解
提要
看别人做的题找到的,联系一下最近做的两道CF和模拟赛题很自然地就会了。
一种不需要线段树分治的在线做法,但时空复杂度相对略高。
提示
-
一个显然的想法是考虑把“查询长度为 \(K\) 的区间的最大值+次大值”变成“修改长度为 \(K\) 的区间”。
-
如果所有插入操作和所有删除操作修改的区间相同,也就是只存在“操作A”和“删除操作A”的话,如果此时线段树下传标记不好维护,可以考虑标记永久化。
-
对顶堆是一种支持插入/删除某个值,查询最值的 $\log $ 数据结构,通过使用一般的堆来实现。
题解
维护这样一个线段树,维护的单点信息是:位置 \(i\) 表示 \([i,i+K)\) 区间的最大值和最大值+次大值的相关信息。
那么维护的区间信息是当前区间的 最大值 和 最大的(最大值+次大值) 值。
接下来把替换看成 区间删除/加入 操作,最初建树看成插入操作。
考虑区间修改需要维护一个区间插入标记,使用标记永久化的技术做,这里标记可以采用优先队列的方式存储,也就是节点维护了当前一整个区间都会被插入的一些数的集合,我们在每个节点用一个堆把这些值存起来。
这样对于删除操作也是容易的,因为插入时递归到的区间和删除时一模一样,所以把维护的堆换成支持删除的对顶堆即可。
线段树的信息上传是细节问题,不再赘述,不理解可以见代码。
查询答案即直接调用根节点信息。
时间复杂度 \(\mathcal O(n\log^2 n+q\log^2 n)\),空间复杂度 \(\mathcal O(n\log n)\)。
优化
可以把用于维护标记的优先队列换成配对堆等可以 \(\mathcal O(1)\) 插入,\(\mathcal O(\log n)\) 查询的堆,再稍微修改一下建树的过程就能做到 \(\mathcal O(n\log n+q\log ^2 n )\),空间复杂度不变。
这样做成功优化时间的本质是:只有删除才会导致用到堆的查询,而如果是只有插入可以直接凭借维护最大值就维护出答案(这部分就是“稍微修改建树过程”的优化)。
于是进一步地可以知道,如果所有修改单调递增,那么时间复杂度为 \(\mathcal O(n\log n+q\log n)\) 。
代码
未进行优化的版本,在洛谷上开 O2 可过,实际码量很小,1.16kb。
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool f=false;char ch=getchar();
while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template<typename T, typename ...Args>
inline void read(T &x, Args &...args){read(x),read(args...);}
template<typename T>
inline void write(T x){
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
#define ll long long
#define pc putchar
const int N=2e6+5,M=1e6+5,INF=1e9+7;
int n,m,K,q,a[N];
struct SGT{
priority_queue<int>ins,del;
int Maxs,Max;
int Gettop(){while(!ins.empty()&&!del.empty()&&ins.top()==del.top()) ins.pop(),del.pop();return ins.empty()?0:ins.top();}
}t[N<<2];
void Pushup(int x){
int tmp=t[x].Gettop();
t[x].del.push(tmp);
int tmp1=t[x].Gettop();
t[x].ins.push(tmp);
t[x].Maxs=max(t[x<<1].Maxs,t[x<<1|1].Maxs);
t[x].Maxs=max(t[x].Maxs,max({t[x<<1].Max,t[x<<1|1].Max,tmp1})+tmp);
t[x].Max=max({t[x<<1].Max,t[x<<1|1].Max,tmp});
return ;
}
void Modify(int x,int l,int r,int ql,int qr,int v,int tp){
if(ql<=l&&r<=qr){
if(tp) t[x].ins.push(v);
else t[x].del.push(v);
Pushup(x);
return ;
}
int mid=l+r>>1;
if(ql<=mid) Modify(x<<1,l,mid,ql,qr,v,tp);
if(qr>mid) Modify(x<<1|1,mid+1,r,ql,qr,v,tp);
Pushup(x);
return ;
}
inline void _Solve(){
read(n,K,q);
for(int i=1;i<=n;i++) read(a[i]);
m=n-K+1;
for(int i=1;i<=n;i++) Modify(1,1,m,max(i-K+1,1),min(i,m),a[i],1);
write(t[1].Maxs),pc('\n');
while(q--){
int pos,v;
read(pos,v);
Modify(1,1,m,max(pos-K+1,1),min(pos,m),a[pos],0);
a[pos]=v;
Modify(1,1,m,max(pos-K+1,1),min(pos,m),a[pos],1);
write(t[1].Maxs),pc('\n');
}
return ;
}
signed main(){
int _T=1;while(_T--) _Solve();
return 0;
}
/*
*/