「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;
}
posted @ 2020-03-23 17:22  SxyLimit  阅读(226)  评论(0编辑  收藏  举报