P2824 [HEOI2016/TJOI2016] 排序(线段树)(内附封面)

文章介绍了如何使用线段树处理数字序列的局部排序问题,涉及升序和降序操作,以及如何通过二分查找确定特定位置的数字。

[HEOI2016/TJOI2016] 排序

题目描述

201620162016 年,佳媛姐姐喜欢上了数字序列。因而她经常研究关于序列的一些奇奇怪怪的问题,现在她在研究一个难题,需要你来帮助她。

这个难题是这样子的:给出一个 111nnn 的排列,现在对这个排列序列进行 mmm 次局部排序,排序分为两种:

  • 0 l r 表示将区间 [l,r][l,r][l,r] 的数字升序排序
  • 1 l r 表示将区间 [l,r][l,r][l,r] 的数字降序排序

注意,这里是对下标在区间 [l,r][l,r][l,r] 内的数排序。
最后询问第 qqq 位置上的数字。

输入格式

输入数据的第一行为两个整数 nnnmmmnnn 表示序列的长度,mmm 表示局部排序的次数。

第二行为 nnn 个整数,表示 111nnn 的一个排列。

接下来输入 mmm 行,每一行有三个整数 op,l,r\text{op},l,rop,l,rop\text{op}op000 代表升序排序,op\text{op}op111 代表降序排序, l,rl,rl,r 表示排序的区间。

最后输入一个整数 qqq,表示排序完之后询问的位置

输出格式

输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第 qqq 位置上的数字。

样例 #1

样例输入 #1

6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3

样例输出 #1

5

提示

河北省选2016第一天第二题。

对于 30%30\%30% 的数据,n,m≤1000n,m\leq 1000n,m1000

对于 100%100\%100% 的数据,n,m≤105n,m\leq 10^5n,m1051≤q≤n1\leq q\leq n1qn

线段树真的太好用好调了,所以我什么时候能去给线段树献花圈顺便在坟头蹦迪

大致思路

  • 我们最终只需要一次访问,经过二分答案得到mid,那么数组内只会有两种数,大于等于mid的和小于mid的数。
  • 对于大于等于mid的数我们标为1,小于mid的我们标为0,每次操作也就变成了给01序列排序
  • 整个题也就变成了两个部分,如何维护用线段树维护这个01序列及二分答案的具体实现

线段树维护01序列排序

首先我们要求出当前区间有几个1,设cnt

  • 升序:将[l,r−cnt]赋值0[l,r-cnt]赋值0[l,rcnt]赋值0,[r−cnt+1,r]赋值1[r-cnt+1,r]赋值1[rcnt+1,r]赋值1
  • 降序:将[l,l+cnt−1]赋值1[l,l+cnt-1]赋值1[l,l+cnt1]赋值1,[r,r−cnt]赋值0[r,r-cnt]赋值0[r,rcnt]赋值0
    举个例子,对于序列
    0 0 1 0 1 00\space0\space1\space 0\space 1\space 00 0 1 0 1 0
    其中cnt=2cnt=2cnt=2
    对[2,4]升序排序: 0 0 0 1 1 00\space0\space0\space 1\space 1\space00 0 0 1 1 0
    对[2,4]降序排序:0 1 1 0 0 00\space1\space1\space 0\space 0\space00 1 1 0 0 0

这样,排序问题就被转化为了区间修改,区间查询,单点查询问题。

bool check(int md){
	build(1,1,n,md);
	for(int i=1;i<=m;i++){
		int cnt=query(1,1,n,l[i],r[i]);
		if(op[i]==0){
			update(1,1,n,l[i],r[i]-cnt,0);
			update(1,1,n,r[i]-cnt+1,r[i],1);
		}
		else if(op[i]==1){
			update(1,1,n,l[i]+cnt,r[i],0);
			update(1,1,n,l[i],l[i]+cnt-1,1);
		}
	}
	return query_point(1,1,n,q);
}
	while(ll<=rr){
		int mmid=(ll+rr)>>1;
		if(check(mmid)){
			ll=mmid+1;
		}
		else {
			rr=mmid-1;
		}
	}
	cout<<rr<<endl;

奇妙の二分答案

经过元素与mid比较,建树,操作后再次查询q,若q为1,说明当前数可行,更新L为mid+1,反正更新R为mid-1

bool check(int md){
	build(1,1,n,md);
	for(int i=1;i<=m;i++){
		int cnt=query(1,1,n,l[i],r[i]);
		if(op[i]==0){
			update(1,1,n,l[i],r[i]-cnt,0);
			update(1,1,n,r[i]-cnt+1,r[i],1);
		}
		else if(op[i]==1){
			update(1,1,n,l[i]+cnt,r[i],0);
			update(1,1,n,l[i],l[i]+cnt-1,1);
		}
	}
	return query_point(1,1,n,q);
}

本题的思想非常巧妙,值得借鉴!

AC CODE

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+888;
int n,m,q,l[N],r[N],op[N];
int sm[N<<2],tag[N<<2],a[N];
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define int long long int 
void pushup(int x){
	sm[x]=sm[lc(x)]+sm[rc(x)];
}
void build(int x,int l,int r,int midd){
	tag[x]=0;
	if(l==r){
		if(a[l]<midd){
			sm[x]=0;
		}
		else sm[x]=1;
		return;
	}
	int mid=(l+r)>>1;
	build(lc(x),l,mid,midd);
	build(rc(x),mid+1,r,midd);
	pushup(x);
}
void cover(int x,int l,int r,int ad){	
	sm[x]+=(r-l+1)*ad;
	if(ad==1) tag[x]=ad;
	else tag[x]=-1;
}
void pushdown(int x,int l,int r){
	if(!tag[x])return;
	int mid=(l+r)>>1;
//	cover(lc(x),l,mid,tag[x]);
//	cover(rc(x),mid+1,r,tag[x]);
	tag[lc(x)]=tag[rc(x)]=tag[x];
	if(tag[x]==1){
		sm[lc(x)]=mid-l+1;
		sm[rc(x)]=r-mid;
	}
	else {
		sm[lc(x)]=sm[rc(x)]=0;	
	}
	tag[x]=0;
}
void update(int x,int l,int r,int L,int R,int ad){
	if(l>R||r<L)return;
	if(l>=L&&R>=r){
		//cover(x,l,r,ad);
		sm[x]=(r-l+1)*ad;
		if(ad==1){
			tag[x]=1;
		}
		else tag[x]=-1;
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	update(lc(x),l,mid,L,R,ad);
	update(rc(x),mid+1,r,L,R,ad);
	pushup(x);
}
int query(int x,int l,int r,int L,int R){
	if(l>R||r<L)return 0;
	if(l>=L&&R>=r){
		return sm[x];
	}
	int mid=(l+r)>>1;
	pushdown(x,l,r);
	return query(lc(x),l,mid,L,R)+query(rc(x),mid+1,r,L,R);
}
int query_point(int x,int l,int r,int xy){
//	if(l>xy||r<xy)return 0;
	if(l==r&&l==xy){
		return sm[x];
	}
	int mid=(l+r)>>1;
	pushdown(x,l,r);
	if(xy<=mid) return query_point(lc(x),l,mid,xy);
	else return query_point(rc(x),mid+1,r,xy);
}
bool check(int md){
	build(1,1,n,md);
	for(int i=1;i<=m;i++){
		int cnt=query(1,1,n,l[i],r[i]);
		if(op[i]==0){
			update(1,1,n,l[i],r[i]-cnt,0);
			update(1,1,n,r[i]-cnt+1,r[i],1);
		}
		else if(op[i]==1){
			update(1,1,n,l[i]+cnt,r[i],0);
			update(1,1,n,l[i],l[i]+cnt-1,1);
		}
	}
	return query_point(1,1,n,q);
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>op[i]>>l[i]>>r[i];
	}
	cin>>q;
	int ll=0,rr=1999990;
	while(ll<=rr){
		int mmid=(ll+rr)>>1;
		if(check(mmid)){
			ll=mmid+1;
		}
		else {
			rr=mmid-1;
		}
	}![请添加图片描述](https://img-blog.csdnimg.cn/95443962d73e4da588fa06edf7ca3579.png)

	cout<<rr<<endl;
	return 0;
}

完结撒花~

附封面(古河渚)

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值