「P2824 [HEOI2016/TJOI2016]排序」在线做法
分享一种本题不用STL的在线做法.
题目大意
给出一个 \(1\) 到 \(n\) 的排列,区间排序和单点查询.
分析
本题有一种通过二分做到的 \(\mathcal{O}(mlog_2^2n)\) 的做法,但是理解起来比较麻烦,而且时间复杂度也比较高,而且只可以查询一个数,甚至不可以在线做,显然这个方法并不优秀,那么是否存在一种容易理解且有着优秀时间复杂度的做法呢,这显然是有的.
先要了解一下线段树分裂这个东西,可以看看这道题,可以算是线段树分裂的模板题,实现起来也比较简单,但是这道题中的分裂和那道题又有所不同,具体下面会讲到.
先考虑如何排序,排序中有一种排序是桶排,那么考虑如何维护桶排呢,自然就会想到权值线段树,对于每一段有序的区间都开一颗权值线段树去维护就好了,如果需要将一段区间排序,那么这段区间的起点和终点可能是在一段有序数列的中间,这时就需要将这颗线段树分裂开来分裂开来的区间自然也是有序的,分裂之后排序的区间就被分成了若干个连续且不重合的区间,每个区间都是一颗权值线段树,那么只要线段树合并一下就好了.
关于查询某段区间内涉及到的权值线段树的根节点编号
这个东西可以用set维护,但是为了照顾其他语言的选手(其实就是我不会),有一种很简单的线段树的维护方法.线段树中每个节点维护两个东西,一个是当前区间内的数是否相同,还有一个是如果相同,则数是什么.然后查询的时候只要这段查询区间内的节点上遍历就好了,如果当前位置已经全部相同就没有必要向下遍历,不同就继续向下遍历.
这里的分裂是分裂范围不固定的,只要求分裂出的树中恰好有 \(k\) 个数(从大到小或从小到大的前 \(k\) 个数).
关于时间复杂度证明
关于上面这个方法的时间复杂度证明:
(我太菜了,证明未必对)
可以发现每次修改之后修改涉及到的区间最多只会变成三个区间,每次查询的时候一个连续相同的区间最多只会被查询到 \(log_2n\) 次,所以均摊时间复杂度就是 \(\mathcal{O}(log_2n)\).
关于线段树合并的时间复杂度证明:
线段树合并的时间复杂度是总共线段树上节点的个数,因为每次合并两个点就会减少一个节点,每次分裂只会多出 \(log_2n\) 个节点,开始有 \(nlog_2n\) 个节点,所以均摊每次操作的时间复杂度是 \(\mathcal{O}(log_2n)\).
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=1e5+7;
int n,m;
int arr[MAXN];
int first_root[MAXN];
int change_root[MAXN];//修改的线段树的根节点
int change_cnt;//修改的线段树的个数
namespace root//两颗线段树,防止重变量名
{
struct LazyTag//区间覆盖的懒标记
{
int check_cover;
int cover;
void CleanLazyTag()
{
check_cover=0;
cover=0;
}
}for_make;
LazyTag MakeLazyTag(int cover)
{
for_make.check_cover=1;
for_make.cover=cover;
return for_make;
}
struct SegmentTree
{
bool check;//判断区间内的数是否相同
int num;//相同则记录这个数
LazyTag tag;
}sgt[MAXN*4];
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
void PushUp(int now)
{
sgt[now].check=(sgt[LSON].check&sgt[RSON].check)&(sgt[LSON].num==sgt[RSON].num);
//如果子树都是全部相同且子树的数相同
if(sgt[now].check)//如果全部相同则记录一下数
{
sgt[now].num=sgt[LSON].num;
}
}
void Build(int now=1,int left=1,int right=n)//建树部分
{
sgt[now].tag.CleanLazyTag();
if(left==right)
{
sgt[now].num=first_root[left];
sgt[now].check=1;
return;
}
Build(LEFT);
Build(RIGHT);
PushUp(now);
}
void Down(LazyTag tag,int now)//标记下传的修改
{
sgt[now].num=tag.cover;
sgt[now].check=1;
sgt[now].tag.cover=tag.cover;
sgt[now].tag.check_cover=1;
}
void PushDown(int now)//下传标记
{
if(sgt[now].tag.check_cover)//如果有覆盖才下传
{
Down(sgt[now].tag,LSON);
Down(sgt[now].tag,RSON);
sgt[now].tag.CleanLazyTag();
}
}
void Updata(int now_left,int now_right,int cover,int now=1,int left=1,int right=n)//区间覆盖
{
if(now_right<left||right<now_left)
{
return;
}
if(now_left<=left&&right<=now_right)
{
Down(MakeLazyTag(cover),now);
return;
}
PushDown(now);
Updata(NOW,cover,LEFT);
Updata(NOW,cover,RIGHT);
PushUp(now);
}
int last_visit;
void Query(int now_left,int now_right,int now=1,int left=1,int right=n)//查询部分
{
if(now_right<left||right<now_left)
{
return;
}
if(now_left<=left&&right<=now_right)
{
if(sgt[now].check)//如果查询到区间内的数全部相同则不用继续向下
{
if(sgt[now].num^last_visit)//如果和上一次查询的结果不同则放入数组
{
change_root[++change_cnt]=sgt[now].num;
last_visit=sgt[now].num;
}
return;
}
}
PushDown(now);//下传标记
Query(NOW,LEFT);
Query(NOW,RIGHT);
}
void QueryRoot(int now_left,int now_right)
{
last_visit=0;//初始化
change_cnt=0;
Query(NOW);//查询
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
}
namespace sort
{
struct Range//记录每个区间的信息
{
int left,right;//尅是和结束位置
bool order;//升序还是降序
}range[MAXN*32];
struct SegmentTree//动态开点权值线段树,需要维护区间和
{
int lson,rson,sum;
}sgt[MAXN*32];
#define LSON sgt[now].lson
#define RSON sgt[now].rson
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
int cnt=0,tot=0;
int rubbish[MAXN*32];
int NewNode()//建一个新节点
{
if(tot)
{
return rubbish[tot--];
}
return ++cnt;
}
void DeleteNode(int &now)//空间回收
{
sgt[now].lson=sgt[now].rson=sgt[now].sum=0;
rubbish[++tot]=now;
now=0;
}
void PushUp(int now)//合并
{
sgt[now].sum=sgt[LSON].sum+sgt[RSON].sum;
}
void SplitFormerKth(int &tree1,int &tree2,int k,int left=1,int right=n)//分裂从小到大前k个数
{
if(k<=0)
{
return;
}
if(sgt[tree1].sum<=k)//如果当前查找的k大于当前区间大小就可以将原树中的这部分放入新树了
{
tree2=tree1;
tree1=0;
return;
}
tree2=NewNode();
int sum=sgt[sgt[tree1].lson].sum;
SplitFormerKth(sgt[tree1].lson,sgt[tree2].lson,k,left,MIDDLE);//想查询kth一样查询
SplitFormerKth(sgt[tree1].rson,sgt[tree2].rson,k-sum,MIDDLE+1,right);
PushUp(tree1);
PushUp(tree2);
}
void SplitLastKth(int &tree1,int &tree2,int k,int left=1,int right=n)//分裂从大到小前k个数
{
if(k<=0)//做法同理
{
return;
}
if(sgt[tree1].sum<=k)
{
tree2=tree1;
tree1=0;
return;
}
tree2=NewNode();
int sum=sgt[sgt[tree1].rson].sum;
SplitLastKth(sgt[tree1].rson,sgt[tree2].rson,k,MIDDLE+1,right);
SplitLastKth(sgt[tree1].lson,sgt[tree2].lson,k-sum,left,MIDDLE);
PushUp(tree1);
PushUp(tree2);
}
void Merge(int &tree1,int &tree2,int left=1,int right=n)//线段树合并
{
if(!tree1||!tree2)//如果两棵树中有一棵树没有当前节点就直接用有的那棵
{
tree1+=tree2;
return;
}
Merge(sgt[tree1].lson,sgt[tree2].lson,left,MIDDLE);//向下递归合并
Merge(sgt[tree1].rson,sgt[tree2].rson,MIDDLE+1,right);
DeleteNode(tree2);//tree2以后不会再用到了,就可以删了,节省空间
PushUp(tree1);
}
void Updata(int num,int val,int &now,int left=1,int right=n)//单点修改,建树时用,所以其实没什么用
{
if(num<left||right<num)
{
return;
}
if(!now)
{
now=NewNode();
}
if(left==right)
{
sgt[now].sum+=val;
return;
}
Updata(num,val,LEFT);
Updata(num,val,RIGHT);
PushUp(now);
}
void Build()//建树
{
REP(i,1,n)//一个点就是一个有序的区间,暴力建树
{
first_root[i]=0;
Updata(arr[i],1,first_root[i]);
range[first_root[i]].left=range[first_root[i]].right=i;
range[first_root[i]].order=0;
}
root::Build();//还要把区间部分树也建一下
}
int new_tree;
void SplitFKth(int &tree1,int &tree2,int k)//分裂区间前k个
{
if(range[tree1].order==0)//需要判断降序还是升序
{
SplitFormerKth(tree1,tree2,k);
}
else
{
SplitLastKth(tree1,tree2,k);
}
}
void SplitLKth(int &tree1,int &tree2,int k)//同理取出后k个
{
if(range[tree1].order==1)
{
SplitFormerKth(tree1,tree2,k);
}
else
{
SplitLastKth(tree1,tree2,k);
}
}
int first_root,last_root;
void Sort(int now_left,int now_right,int order)
{
root::QueryRoot(now_left,now_right);//先查询出设计到的区间的线段树的根节点
first_root=0;//前面多出的部分
last_root=0;//后面多出的部分
if(now_left-range[change_root[1]].left)//如果前面有多出部分就分裂出来
{
SplitFKth(change_root[1],first_root,now_left-range[change_root[1]].left);//分裂多出部分
range[first_root].left=range[change_root[1]].left;//新的区间左边和开始区间相同
range[first_root].right=now_left-1;//右边为修改区间边上
range[first_root].order=range[change_root[1]].order;//排序方式和原来相同
root::Updata(range[first_root].left,range[first_root].right,first_root);//重新覆盖上新的节点编号
}
if(range[change_root[change_cnt]].right-now_right)//后面同理
{
SplitLKth(change_root[change_cnt],last_root,range[change_root[change_cnt]].right-now_right);
range[last_root].left=now_right+1;
range[last_root].right=range[change_root[change_cnt]].right;
range[last_root].order=range[change_root[change_cnt]].order;
root::Updata(range[last_root].left,range[last_root].right,last_root);
}
REP(i,2,change_cnt)//全部合并起来
{
Merge(change_root[1],change_root[i]);
}
range[change_root[1]].left=now_left;//新区间范围就是修改的范围
range[change_root[1]].right=now_right;
range[change_root[1]].order=order;//排序方式也是修改方式
root::Updata(now_left,now_right,change_root[1]);
}
int QueryKth(int k,int now,int left=1,int right=n)//查询k大,没什么可以说的
{
if(left==right)
{
return left;
}
if(sgt[sgt[now].lson].sum>=k)
{
return QueryKth(k,LEFT);
}
else
{
return QueryKth(k-sgt[sgt[now].lson].sum,RIGHT);
}
}
int Query(int k)//查询第k个位置
{
root::QueryRoot(k,k);//这道区间
if(range[change_root[1]].order==0)//分类查询kth
{
return QueryKth(k-range[change_root[1]].left+1,change_root[1]);
}
else
{
return QueryKth(range[change_root[1]].right-k+1,change_root[1]);
}
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
}
int main()
{
scanf("%d%d",&n,&m);
REP(i,1,n)
{
scanf("%d",&arr[i]);
}
sort::Build();
int left,right,order,k;
REP(i,1,m)
{
scanf("%d%d%d",&order,&left,&right);
sort::Sort(left,right,order);
}
scanf("%d",&k);
printf("%d",sort::Query(k));
return 0;
}