BZOJ4552: [Tjoi2016&Heoi2016]排序 线段树

BZOJ4552: [Tjoi2016&Heoi2016]排序

Time Limit: 60 Sec   Memory Limit: 256 MB
Submit: 587   Solved: 327

题解:
首先发现,题目是对区间进行了一系列操作,我们可以想到线段树,但是怎么来维护呢?
我们首先可以二分答案,然后把原数列转化一下,把原数列中的数值大于等于答案的设成1,把小于答案的设成0
这样在线段树上就可以操作了,因为升序或者降序排列,会把这段区间变成一边全是0,一边全是1。
所以只需要区间求1的个数和,和区间覆盖两个操作即可。
最后判断p位置处是0还是1,如果是0,那么说明答案应该比当前答案小,否则应该大于等于当前答案,继续二分即可,时间 复杂度 O(nlogn) 
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,a[N],o[N],L[N],R[N],p[N],pos;
struct SegmentTree{int l,r,len,sum,lazy;}T[N<<2];
void Pushup(int x)
{
	T[x].sum=T[x<<1].sum+T[x<<1|1].sum;
}
void Pushdown(int x)
{
	int lc=x<<1,rc=x<<1|1;
	if(T[x].lazy==0) T[lc].sum=T[rc].sum=0;
	else T[lc].sum=T[lc].len,T[rc].sum=T[rc].len;
	T[lc].lazy=T[rc].lazy=T[x].lazy;
	T[x].lazy=-1;
}
void Build(int x,int l,int r)
{
	T[x].l=l,T[x].r=r,T[x].len=(r-l+1);
	T[x].lazy=-1;
	if(l==r)
	{
		T[x].sum=p[l];
		return;
	}
	int mid=(l+r)>>1;
	Build(x<<1,l,mid);
	Build(x<<1|1,mid+1,r);
	Pushup(x);
}
int Query(int x,int l,int r)
{
	if(T[x].l==l&&T[x].r==r) return T[x].sum;
	if(T[x].lazy!=-1) Pushdown(x);
	int mid=(T[x].l+T[x].r)>>1;
	if(r<=mid) return Query(x<<1,l,r);
	else if(l>mid) return Query(x<<1|1,l,r);
	else return Query(x<<1,l,mid)+Query(x<<1|1,mid+1,r);
}
void Modify(int x,int l,int r,int v)
{
	if(T[x].l==l&&T[x].r==r)
	{
		if(v==1) T[x].sum=T[x].len,T[x].lazy=1;
		else T[x].sum=0,T[x].lazy=0;
		return;
	}
	if(T[x].lazy!=-1) Pushdown(x);
	int mid=(T[x].l+T[x].r)>>1;
	if(r<=mid) Modify(x<<1,l,r,v);
	else if(l>mid) Modify(x<<1|1,l,r,v);
	else Modify(x<<1,l,mid,v),Modify(x<<1|1,mid+1,r,v);
	Pushup(x);
}
int Judge(int x)
{
	for(int i=1;i<=n;i++)
	{
		if(a[i]>=x) p[i]=1;
		else p[i]=0;
	}
	Build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int tmp=Query(1,L[i],R[i]);
		if(tmp==0||tmp==R[i]-L[i]+1) continue;
		if(o[i]==0) Modify(1,R[i]-tmp+1,R[i],1),Modify(1,L[i],R[i]-tmp,0);
		else Modify(1,L[i],L[i]+tmp-1,1),Modify(1,L[i]+tmp,R[i],0);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&o[i],&L[i],&R[i]);
	scanf("%d",&pos);
	int l=1,r=n,mid,ans;
	while(l<=r)
	{ 
		mid=(l+r)>>1;
		Judge(mid);
		if(Query(1,pos,pos)==1) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d\n",ans);
}

Description

在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题
,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排
序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q
位置上的数字。

Input

输入数据的第一行为两个整数n和m。n表示序列的长度,m表示局部排序的次数。1 <= n, m <= 10^5第二行为n个整
数,表示1到n的一个全排列。接下来输入m行,每一行有三个整数op, l, r, op为0代表升序排序,op为1代表降序
排序, l, r 表示排序的区间。最后输入一个整数q,q表示排序完之后询问的位置, 1 <= q <= n。1 <= n <= 10^5
,1 <= m <= 10^5

Output

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

Sample Input

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

Sample Output

5

HINT

Source

[Submit][Status][Discuss]
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值