luogu P2824排序题解
Posted quitter
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luogu P2824排序题解相关的知识,希望对你有一定的参考价值。
题目描述
在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q位置上的数字。
输入格式
输入数据的第一行为两个整数n和m。n表示序列的长度,m表示局部排序的次数。第二行为n个整数,表示1到n的一个全排列。接下来输入m行,每一行有三个整数op, l, r, op为0代表升序排序,op为1代表降序排序, l, r 表示排序的区间。最后输入一个整数q,q表示排序完之后询问的位置
输出格式
输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第q位置上的数字。
输入输出样例
6 3 1 6 2 5 3 4 0 1 4 1 3 6 0 2 4 3
5
说明/提示
河北省选2016第一天第二题。
对于30%的数据,n,mleq 1000n,m≤1000
对于100%的数据,n,mleq 10^5n,m≤105且始终1leq qleq n1≤q≤n
题意简单明了,但题目给了我一个很棒的思路。
先想想暴力该怎么打。
排序?我们看到了n的取值范围只有1e5,这显然给桶排带来了无限生机。记录下区间的min和max,这题可以暴力出80分的好成绩。
虽然80分对我来说已经够了, 但是100分总是那么诱人。
我们观察题目中给出的一些特殊性质。
1、 对于[1,n]中每个元素只出现一次。
2、 查询只有1次。
这两个性质足够我们打出正解了。
首先答案一定在[1,n]中。能不能二分答案?
我们把操作离线,对于每个mid进行check。
如何check?首先,我们把原序列中大于等于mid的标为1,小于mid的记做0。
这样我们一趟排序下来,如果pos(查询的位置)为1,这代表说,pos是大于等于mid的,答案一定在[mid,r]中。
反之,答案一定在[l,mid-1]中。
那么这样做好在哪里呢?
对于普通排序,顶多做到O(n),然而对于0,1序列的排序,可以做到O(log n)。
如何操作?
可以使用强大的线段树。线段树的区间查询,区间修改,可以用于0,1序列的排序。
当对一个区间进行排序时,首先query出这个区间里1的个数,也就是区间和,记做cnt。
若是降序排列,则把区间[l,l+cnt-1]修改为1,[l+cnt,r]修改为0。
若是升序排列,则把区间[r-cnt+1,r]修改为1,[l,r-cnt]修改为0。
上面就是基于线段树对0,1串的排序。
总体时间复杂度O(mlognlogn)
线段树的功能十分强大,区间修改是线段树最重要的功能之一,好好学习和运用,会有很大的帮助。
#include<iostream> #include<cstdio> #include<cstring> #define ls (k<<1) #define rs (k<<1|1) using namespace std; const int N=1e5+10; int tree[N<<2],tag[N<<2],a[N],op[N][5];//原先因为tag忘开4倍空间只有80分,给自己长个记性。 int n,m,pos; inline void read(int &x){ x=0; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); } inline void build(int k,int x,int y,int val){ if(x==y){ tree[k]=(a[x]>=val?1:0); return; } int mid=x+y>>1; build(ls,x,mid,val); build(rs,mid+1,y,val); tree[k]=tree[ls]+tree[rs]; } inline void update(int k,int x,int y){ int mid=x+y>>1; tree[ls]=tag[k]*(mid-x+1); tree[rs]=tag[k]*(y-mid); tag[ls]=tag[k]; tag[rs]=tag[k]; tag[k]=-1; } inline int query(int k,int x,int y,int l,int r){ if(x>r||y<l) return 0; if(x>=l&&y<=r) return tree[k]; if(tag[k]!=-1) update(k,x,y); int mid=x+y>>1; int a=query(ls,x,mid,l,r); int b=query(rs,mid+1,y,l,r); return a+b; } inline void change(int k,int x,int y,int l,int r,int val){ if(x>r||y<l) return; if(x>=l&&y<=r){ tag[k]=val; tree[k]=(y-x+1)*val; return; } if(tag[k]!=-1) update(k,x,y); int mid=x+y>>1; change(ls,x,mid,l,r,val); change(rs,mid+1,y,l,r,val); tree[k]=tree[ls]+tree[rs]; } inline bool check(int x){ int i,cnt,l,r; memset(tag,-1,sizeof(tag)); build(1,1,n,x); for(i=1;i<=m;i++){ l=op[i][1],r=op[i][2]; cnt=query(1,1,n,l,r); if(op[i][0]){ change(1,1,n,l,l+cnt-1,1); change(1,1,n,l+cnt,r,0); } else{ change(1,1,n,r-cnt+1,r,1); change(1,1,n,l,r-cnt,0); } } return query(1,1,n,pos,pos); } int main() { int i,j,x,y,t,s,cnt; read(n),read(m); for(i=1;i<=n;i++) read(a[i]); for(i=1;i<=m;i++) read(op[i][0]),read(op[i][1]),read(op[i][2]); int L=1,R=n,ans,mid; read(pos); while(R>=L){ mid=L+R>>1; if(check(mid)) L=mid+1,ans=mid; else R=mid-1; } printf("%d",ans); }
以上是关于luogu P2824排序题解的主要内容,如果未能解决你的问题,请参考以下文章
线段树好题! P2824 [HEOI2016/TJOI2016]排序 题解
线段树合并P2824 [HEOI2016/TJOI2016]排序
(luogu题解搬运系列)luogu p1459 三值的排序