[HEOI2016/TJOI2016]排序
题目大意
给出一个 \(1 \sim n\) 的排列,每次操作将 \({[l_i,r_i]}\) 上的数排序(\(opt=0\)升序,\(opt=1\)降序)
最后有一个询问第\(Q\)位上的数
\(n,m \leq 100000\)
解题思路
这题考虑用二分答案和线段树
设当前二分值为\(v\)
将排列中的数\(<v\)的设成\(0\),\(>=v\)的设成\(1\),丢给线段树(\(O(n)\)建树)
每次排序即统计这个区间内\(0\)和\(1\)的个数(相当于在线段树中的询问操作),再按照个数覆盖回去
操作完后查询第\(Q\)位上的数
若为\(1\),则答案\(>=v\)
若为\(0\),则答案\(<v\)
举个例子
给定排列1 6 2 5 3 4
当前二分值\(4\)
按上面的方式写成01序列变成0 1 0 1 0 1
此时对\([1,4]\)升序排列变成0 0 1 1 0 1
只要把\(1\)覆盖后面部分,\(0\)覆盖前面部分就完成了排序操作
可以用线段树区间修改维护
注意到,如果最终第\(Q\)位是1,表明这一位至少\(>=4\),这成为了我们继续二分的依据
#include<iostream>
#include<cstdio>
#include<cstring>
const int maxN=200000,Tsize=10000000;
int n,m,S[maxN],opt[maxN],l[maxN],r[maxN],Q;
int T[Tsize],tag[Tsize];
void build(int o,int l,int r,int v){
if (l==r){
T[o]=S[l]>=v;
return;
}
int mdl=(l+r)>>1;
build(o<<1,l,mdl,v);
build(o<<1|1,mdl+1,r,v);
T[o]=T[o<<1]+T[o<<1|1];
}
void pushdown(int now,int size){
if (~tag[now]){
T[now]=(tag[now])?size:0;
tag[now<<1]=tag[now<<1|1]=tag[now];
tag[now]=-1;
}
}
int query(int o,int l,int r,int L,int R){
if (l>r) return 0;
if (l>R||r<L) return 0;
pushdown(o,r-l+1);
if (L<=l&&r<=R) return T[o];
int mdl=(l+r)>>1;
return query(o<<1,l,mdl,L,R)+query(o<<1|1,mdl+1,r,L,R);
}
int modify(int o,int l,int r,int L,int R,int v){
if (l>r) return 0;
pushdown(o,r-l+1);
if (l>R||r<L) return T[o];
if (L<=l&&r<=R){tag[o]=v;return (v)?(r-l+1):0;}
int mdl=(l+r)>>1;
return T[o]=modify(o<<1,l,mdl,L,R,v)+modify(o<<1|1,mdl+1,r,L,R,v);
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&S[i]);
for (int i=1;i<=m;i++) scanf("%d%d%d",&opt[i],&l[i],&r[i]);
scanf("%d",&Q);
int L=1,R=n,mdl;
while (L<R){
mdl=(L+R+1)>>1;
memset(T,0,sizeof(T));
memset(tag,-1,sizeof(tag));
build(1,1,n,mdl);
for (int i=1;i<=m;i++){
int cnt=query(1,1,n,l[i],r[i]);
if (opt[i]){
modify(1,1,n,l[i],l[i]+cnt-1,1);
modify(1,1,n,l[i]+cnt,r[i],0);
}
else{
modify(1,1,n,l[i],r[i]-cnt,0);
modify(1,1,n,r[i]-cnt+1,r[i],1);
}
}
int now=query(1,1,n,Q,Q);
if (now) L=mdl;
else R=mdl-1;
}
printf("%d",L);
}