DZY Loves Sorting HDU - 5649 线段树合并和分裂

Part 0

这道题就是[Tjoi2016&Heoi2016]排序的升级版。可以直接改成多Case过去的。

本题有两种做法,一种是普通线段树+二分(复杂度 n ⋅ log ⁡ 2 ( n ) n \cdot \log^{2}(n) nlog2(n))

还有的就是现在要讲的线段树合并+线段树分裂(复杂度 n ⋅ log ⁡ 2 ( n ) n \cdot \log^{2} (n) nlog2(n))

Part 1

其实讲一个区间排序后,这个区间就成了有序区间,此时我们只用知道这个区间有什么元素,还有这个区间内是逆序还是顺序,即可知道这个区间每个位置的元素。由这个性质即可联想到线段树合并.每个集合开一颗线段树,在操作时进行合并和分离。

Part 2

一开始每个数都是一个有序集合,在一次次合并之后可能变为如下情况。(数字指的是下标)

{1-3} {4-7}{7-9}{9-13}

PS:区间的维护可以用set存每个区间的起始位置,这样可以快速确定每个点所在区间的位置

假设我们要进行 0   6   12 0 \ 6 \ 12 0 6 12这个操作那么我们可以将{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

空间大概估计(估的不是很准确)

一开始初始化后的 n log ⁡ n n \log n nlogn+操作合并的 n log ⁡ n n \log n nlogn+操作分裂的 n log ⁡ n n \log n nlogn(很可能不满)

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值