Part 0
这道题就是[Tjoi2016&Heoi2016]排序的升级版。可以直接改成多Case过去的。
本题有两种做法,一种是普通线段树+二分(复杂度 )
还有的就是现在要讲的线段树合并+线段树分裂(复杂度 )
Part 1
其实讲一个区间排序后,这个区间就成了有序区间,此时我们只用知道这个区间有什么元素,还有这个区间内是逆序还是顺序,即可知道这个区间每个位置的元素。由这个性质即可联想到线段树合并.每个集合开一颗线段树,在操作时进行合并和分离。
Part 2
一开始每个数都是一个有序集合,在一次次合并之后可能变为如下情况。(数字指的是下标)
{1-3} {4-7}{7-9}{9-13}
PS:区间的维护可以用set存每个区间的起始位置,这样可以快速确定每个点所在区间的位置
假设我们要进行 这个操作那么我们可以将{4-7}这个集合分裂为{4-5}和{6-7},将{9-13}分裂为{9-12}和{12-13}。再将{6-7},{7-9},{9-12}三个集合合并即可。
(总而言之就是把当前集合分裂使{L-1}与{L}不在同一集合内且{R}与{R+1}不在同一集合内)
讲解一下线段树分裂
由于{4-7}这个集合是有序的,假设是逆序的。那么我们分裂出的{6-7}就是{4-7}这个集合中较小的两个元素。
其代码如下
void UpDevide(int L,int R,int K,int oid,int &tid){
Create(tid);
if(L==R){
cnt[tid]=cnt[oid];//转移
cnt[oid]=0;//注意清空原来的
return;
}
int mid=(L+R)>>1,res=cnt[Lson[oid]];
if(K<=res){
UpDevide(L,mid,K,Lson[oid],Lson[tid]);
Up(oid),Up(tid);
return;
}
Lson[tid]=Lson[oid],Lson[oid]=0;//左半边区间直接转移
UpDevide(mid+1,R,K-res,Rson[oid],Rson[tid]);
Up(oid),Up(tid);
}
最后查询答案和区间第K值的操作类似。
Part3
空间大概估计(估的不是很准确)
一开始初始化后的 +操作合并的 +操作分裂的 (很可能不满)
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define M 100005
using namespace std;
set<int>st;
set<int>::iterator it,it0;
int A[M],n;
bool OP[M];//这个集合是顺序还是逆序
int Find_L(int x){//返回x所在区间左端点
it=st.upper_bound(x);
return *(--it);
}
int Find_R(int x){//返回x所在区间右端点
it=st.upper_bound(x);
return (*it)-1;
}
struct SegTree{
int tot,Root[M];
int Lson[M*60],Rson[M*60],cnt[M*60];
void clear(){
tot=0;
for(int i=0;i<=n;i++)Root[i]=0;//多Case,但是n总和不大,这样不会TLE
}
void Create(int &tid){//新建节点
tid=++tot;
Lson[tid]=Rson[tid]=cnt[tid]=0;
}
void Up(int tid){
cnt[tid]=cnt[Lson[tid]]+cnt[Rson[tid]];
}
void Updata(int L,int R,int x,int &tid){
if(!tid)Create(tid);
cnt[tid]++;
if(L==R)return;
int mid=(L+R)>>1;
if(x<=mid)Updata(L,mid,x,Lson[tid]);
else Updata(mid+1,R,x,Rson[tid]);
}
void Merge(int &x,int y){
if(x==y)return;
if(!x||!y)return (void)(x+=y);
Merge(Lson[x],Lson[y]);
Merge(Rson[x],Rson[y]);
cnt[x]+=cnt[y];
}
void UpDevide(int L,int R,int K,int oid,int &tid){//将oid中前K值分给tid
Create(tid);
if(L==R){
cnt[tid]=cnt[oid];//转移
cnt[oid]=0;//注意清空原来的
return;
}
int mid=(L+R)>>1,res=cnt[Lson[oid]];
if(K<=res){
UpDevide(L,mid,K,Lson[oid],Lson[tid]);
Up(oid),Up(tid);
return;
}
Lson[tid]=Lson[oid],Lson[oid]=0;//左半边区间直接转移
UpDevide(mid+1,R,K-res,Rson[oid],Rson[tid]);
Up(oid),Up(tid);
}
void DownDevide(int L,int R,int K,int oid,int &tid){
Create(tid);
if(L==R){
cnt[tid]=cnt[oid];
cnt[oid]=0;
return;
}
int mid=(L+R)>>1,res=cnt[Rson[oid]];
if(K<=res){
DownDevide(mid+1,R,K,Rson[oid],Rson[tid]);//先跑到右区间
Up(oid),Up(tid);
return;
}
Rson[tid]=Rson[oid],Rson[oid]=0;
DownDevide(L,mid,K-res,Lson[oid],Lson[tid]);
Up(oid),Up(tid);
}
int Query(int L,int R,int K,int tid){
if(L==R)return L;
int mid=(L+R)>>1,res=cnt[Lson[tid]];
if(K<=res)return Query(L,mid,K,Lson[tid]);
else return Query(mid+1,R,K-res,Rson[tid]);
}
}ST;
void Init(){
st.clear();
ST.clear();
}
int main(){
int T;
scanf("%d",&T);
while(T--){
Init();
int m;
scanf("%d%d",&n,&m);
st.insert(n+1);
for(int i=1;i<=n;i++)scanf("%d",&A[i]);
for(int i=1;i<=n;i++){
st.insert(i);
ST.Updata(1,n,A[i],ST.Root[i]);
OP[i]=0;//初始都为顺序
}
while(m--){
int op,L,R;
scanf("%d%d%d",&op,&L,&R);
int Lx=Find_L(L),Rx=Find_R(L);//查询左端点所在区间
if(Lx!=L){//如果所在区间越过了L
int p=0;
if(!OP[Lx])ST.DownDevide(1,n,Rx-L+1,ST.Root[Lx],p);
else ST.UpDevide(1,n,Rx-L+1,ST.Root[Lx],p);
st.insert(L);
OP[L]=OP[Lx];//注意此处OP要顺带转移
ST.Merge(ST.Root[L],p);
}
Lx=Find_L(R),Rx=Find_R(R);
if(Rx!=R){//如果所在区间越过了R
int p=0;
if(!OP[Lx])ST.DownDevide(1,n,Rx-R,ST.Root[Lx],p);
else ST.UpDevide(1,n,Rx-R,ST.Root[Lx],p);
st.insert(R+1);
OP[R+1]=OP[Lx];
ST.Merge(ST.Root[R+1],p);
}
it=st.find(L);
it++;
while((*it)<=R){//将[L,R]内的集合合并
ST.Merge(ST.Root[L],ST.Root[(*it)]);
ST.Root[(*it)]=0;
it0=it;
it++;
st.erase(it0);
}
OP[L]=op;//注意要最后才进行这步操作
}
int x;
scanf("%d",&x);
int Lx=Find_L(x),Rx=Find_R(x);
if(!OP[Lx])printf("%d\n",ST.Query(1,n,x-Lx+1,ST.Root[Lx]));//利用区间第K值求答案
else printf("%d\n",ST.Query(1,n,Rx-x+1,ST.Root[Lx]));
}
return 0;
}