LGP2824 [HETS 2016||TJTS 2016] 排序 学习笔记
LGP2824 [HETS 2016||TJTS 2016] 排序 学习笔记
题意简述
给出一个长为 \(n\) 的排列 \(A\),对其进行 \(m\) 次局部排序,排序分为两种:
0 l r表示将区间 \([l,r]\) 的数字按升序排序;1 l r表示将区间 \([l,r]\) 的数字按降序排序。
这里的区间指的是下标而非值。
最后一次询问,询问上述所有操作依次完成后 \(A_Q\) 的值。
做法解析
发现只有一次询问,而且还是在所有操作之后。套路地考虑二分答案 \(mid\):对于每次二分,将所有大于等于 \(mid\) 的元素都设为 \(1\),小于它的元素设为 \(0\)。
这样子排序操作因为只有 \(0\) 和 \(1\),就从 \(N\logN\) 变成 \(\logN\) 的区间求和和区间赋值了,显然用线段树维护。如果最后目标是 \(1\) 就说明实际答案大于等于 \(mid\),收缩 \(l\),反之亦然。
然后做完了。时间复杂度 \(O(M\log^2 N)\)。
代码实现
#include <bits/stdc++.h>
using namespace std;
namespace obasic{
template <typename _T>
void readi(_T &x){
_T k=1;x=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
x*=k;return;
}
template <typename _T>
void writi(_T x){
if(x<0)putchar('-'),x=-x;
if(x>9)writi(x/10);
putchar(x%10+'0');
}
};
using namespace obasic;
const int MaxN=1e5+5;
int N,M,A[MaxN],Q;
struct oper{int t,l,r;}P[MaxN];
struct SegTree{
int cl[MaxN<<2],cr[MaxN<<2],cmid[MaxN<<2];
int sum[MaxN<<2],tag[MaxN<<2];
int ls(int p){return p<<1;}
int rs(int p){return (p<<1)|1;}
void ainit(int p,int l,int r){
cl[p]=l,cr[p]=r,cmid[p]=(l+r)>>1;if(l==r)return;
ainit(ls(p),l,cmid[p]);ainit(rs(p),cmid[p]+1,r);
}
void pushup(int p){
sum[p]=sum[ls(p)]+sum[rs(p)];
}
void build(int p,int v){
tag[p]=-1;
if(cl[p]==cr[p]){sum[p]=(A[cl[p]]>=v?1:0);return;}
build(ls(p),v),build(rs(p),v);pushup(p);
}
void maketag(int p,int v){
sum[p]=v*(cr[p]-cl[p]+1),tag[p]=v;
}
void pushdown(int p){
if(tag[p]==-1||cl[p]==cr[p])return;
maketag(ls(p),tag[p]);
maketag(rs(p),tag[p]);
tag[p]=-1;
}
int getsum(int p,int dl,int dr){
if(dl<=cl[p]&&cr[p]<=dr)return sum[p];
pushdown(p);int res=0;
if(dl<=cmid[p])res+=getsum(ls(p),dl,dr);
if(dr>cmid[p])res+=getsum(rs(p),dl,dr);
pushup(p);return res;
}
void update(int p,int dl,int dr,int v){
if(dl<=cl[p]&&cr[p]<=dr){maketag(p,v);return;}
pushdown(p);
if(dl<=cmid[p])update(ls(p),dl,dr,v);
if(dr>cmid[p])update(rs(p),dl,dr,v);
pushup(p);
}
}SegTr;
bool check(int lim){
SegTr.build(1,lim);
for(int i=1;i<=M;i++){
auto [pt,pl,pr]=P[i];
int cnt=SegTr.getsum(1,pl,pr);
if(cnt==pr-pl+1)continue;
if(pt==0){
SegTr.update(1,pl,pr-cnt,0);
SegTr.update(1,pr-cnt+1,pr,1);
}
if(pt==1){
SegTr.update(1,pl,pl+cnt-1,1);
SegTr.update(1,pl+cnt,pr,0);
}
}
return SegTr.getsum(1,Q,Q);
}
int main(){
readi(N),readi(M);SegTr.ainit(1,1,N);
for(int i=1;i<=N;i++)readi(A[i]);
for(int i=1;i<=M;i++)readi(P[i].t),readi(P[i].l),readi(P[i].r);
readi(Q);int bl=1,br=N,bmid,ans;
while(bl<=br){
bmid=(bl+br)>>1;
if(check(bmid))ans=bmid,bl=bmid+1;
else br=bmid-1;
}
writi(ans);
return 0;
}
反思总结
如果题目问进行了一堆比较大小相关操作后只跟了一次询问,考虑二分这个询问的结果,然后将大于等于它的东西设为 \(1\),小于它的东西设为 \(0\),抽象为 \(0\) 和 \(1\) 后很多操作都可以在更好的复杂度内完成,如果最后目标是 \(1\) 就说明实际答案大于等于 \(mid\),收缩 \(l\),反之亦然。
另外如果线段树要修改或查询由比较活跃的变量决定的区间的话,注意判空!!!
浙公网安备 33010602011771号