P2824 [HEOI2016/TJOI2016]排序 (思维+排序+线段树+二分)

题目描述

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

输入格式

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

输出格式

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

输入输出样例

输入
6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3
输出
5

说明/提示

河北省选2016第一天第二题。
对于 30%30% 的数据,n,m≤1000
对于 100%100% 的数据,n,m≤105,1≤q≤n

题目分析

这道题需要我们进行m次排序,但排序是很慢的一种算法,直接排序是肯定会超时的。
那么我们来想想哪种序列是可以比较快速的进行排序的:01序列
01序列可以在O(logn)的复杂度内进行排序。方法是:统计01序列中1的个数a,然后直接将序列后a个数赋为1,其余位置赋为0(通过线段树维护可以把复杂度降到logn)。

下一步我们要考虑如何把01序列应用到这道题上。
首先我们可以注意到,这道题只有一个查询。假设这次查询的答案为x,我们可以将序列中大于等于x的数变为1,而小于x的数变为0。这样序列就转化为了一个01序列了。

x的值我们可以枚举获得,但是要枚举获得n的话总时间复杂度会变为O(nmlogn),因此还要进行优化。我们可以发现答案x的值是具有单调性的,因此我们是可以二分x的值的。这样复杂度就变为O(mlog2n),符合要求。

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f;
struct Sort{
    
    			//记录这m次排序操作
	int op,l,r;
}q[N];
struct Node{
    
    			//线段树
	int l,r;			//sum记录01序列中1的个数
	int sum,lazy;		//lazy为懒标记:1代表将此段全部变为1,-1代表将此段全部变为0
}tr[N*4];
int n,m,k,a[N];
void pushup(int u)
{
    
    
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
    
    
	if(tr[u].lazy)
	{
    
    
		tr[u<<1].lazy=tr[u<<1|1].lazy=tr[u].lazy;
		if(tr[u].lazy==1){
    
    
			tr[u<<1].sum=tr[u<<1].r-tr[u<<1].l+1;
			tr[u<<1|1].sum=tr[u<<1|1].r-tr[u<<1|1].l+1;
		}
		else tr[u<<1].sum=tr[u<<1|1].sum=0;
		tr[u].lazy=0;
	}
}
void build(int u,int l,int r,int x)
{
    
    
	if(l==r) tr[u]={
    
    l,r,a[l]>=x,0};			//序列中大于等于x的数变为1,小于x的数变为0
	else {
    
    
		tr[u]={
    
    l,r};
		int mid=l+r>>1; 
		build(u<<1,l,mid,x),build(u<<1|1,mid+1,r,x);
		pushup(u);
	}
}
int query(int u,int l,int r)				//查询[l,r]中1的个数
{
    
    
	if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	int sum=0;
	if(mid>=l) sum=query(u<<1,l,r);
	if(mid<r) sum+=query(u<<1|1,l,r);
	return sum;
}
void update(int u,int l,int r,int c)		//将[l,r]区间中的数全部变为c
{
    
    
	if(l<=tr[u].l&&tr[u].r<=r)
	{
    
    
		tr[u].sum=c*(tr[u].r-tr[u].l+1);
		tr[u].lazy=c?1:-1;
	}
	else {
    
    
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(mid>=l) update(u<<1,l,r,c);
		if(mid<r) update(u<<1|1,l,r,c);
		pushup(u);
	}
}
bool queryPoint(int u,int x)				//查询x位置上的数是否为1
{
    
    
	if(tr[u].l==tr[u].r) return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(x<=mid) return queryPoint(u<<1,x);
	else return queryPoint(u<<1|1,x);
}
bool check(int mid)							//检查此答案值是否合法
{
    
    
	build(1,1,n,mid);						//用此答案值建树
	for(int i=0;i<m;i++)
	{
    
    
		int op=q[i].op,l=q[i].l,r=q[i].r;	//对[l,r]区间进行排序
		int cnt=query(1,l,r);				//查询[l,r]中1的个数
		if(cnt==0||cnt==r-l+1) continue;	//如果区间中的数全部相同,那么不需要进行排序
		if(op)
		{
    
    
			update(1,l,cnt+l-1,1);
			update(1,cnt+l,r,0);
		}
		else 
		{
    
    
			update(1,l,r-cnt,0);
			update(1,r-cnt+1,r,1);
		}
	}
	return queryPoint(1,k);			//所有操作完成后查看k位置上的数是否为1
}
int main()
{
    
    
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=0;i<m;i++)
		scanf("%d%d%d",&q[i].op,&q[i].l,&q[i].r);
	scanf("%d",&k);
	
	int l=1,r=n;					//二分答案x
	while(r>l)
	{
    
    
		int mid=l+r+1>>1;
		if(check(mid)) l=mid;		//如果为真说明此解合法,看看也没有更大的解l=mid
		else r=mid-1;				//如果为假说明此解不合法,减小答案r=mid-1
	}
	printf("%d\n",l);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/li_wen_zhuo/article/details/113035566