[HEOI2016/TJOI2016] 排序 解题报告(二分答案/线段树分裂合并+set)
题目链接:
https://www.luogu.org/problemnew/show/P2824
题目描述:
在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序。最后询问第q位置上的数字。
题解:
做法一:二分答案
然而我并没有写,口胡一下就是二分最终的那个数字,假设我们二分的值是$val$,那么在序列中大于等于$val$我们看成1,小于$val$的我们看成0,这样就是一个01序列了。每次操作我们就线段树维护0的个数和1的个数然后区间赋值就是了。最终我们看一下第q个位置是0还是1,如果是1就把$val$调大,否则调小。
做法二:线段树分裂合并+set
主要讲讲这个吧
考虑我们一开始对每个元素建一颗动态开点权值线段树,因为是1到n的排列我们都不需要离散化(其实本身是动态开点好像也没有很大的影响)。这样我们就得到了一个个由一个元素构成的区间,在之后的操作中然后我们用set维护一下每个区间的左端点,右端点,这个区间线段树的根节点和这个区间是单调增的还是单调减的。
不管是操作1还是操作2,我们就是把[l,r]的所有元素合并到一棵线段树,唯一麻烦的就是端点l,r可能是在当前的某个区间里面,这样的话我们还需要把这个区间给分裂,再把我们需要的那部分拿出来合并
代码实现是这样的,注意我们还要修改一下set
set<Dat>::iterator it=s.lower_bound((Dat){0,l,0,0}); if ((*it).l!=l) { Dat t=*it;s.erase(it);int tn=0; if (t.type==0) { Seg::split(1,n,t.nd,tn,l-t.l); s.insert((Dat){t.l,l-1,tn,0}); s.insert((Dat){l,t.r,t.nd,0}); //printf("qq%d %d\n",t.l,l-1); //printf("qq%d %d\n",l,t.r); } else { Seg::split(1,n,t.nd,tn,t.r-l+1); s.insert((Dat){t.l,l-1,t.nd,1}); s.insert((Dat){l,t.r,tn,1}); } } it=s.lower_bound((Dat){0,r,0,0}); if ((*it).r!=r) { Dat t=*it;s.erase(it);int tn=0; if (t.type==0) { Seg::split(1,n,t.nd,tn,r-t.l+1); //printf("qq%d\n",tn); s.insert((Dat){t.l,r,tn,0}); s.insert((Dat){r+1,t.r,t.nd,0}); } else { Seg::split(1,n,t.nd,tn,t.r-r); s.insert((Dat){t.l,r,t.nd,1}); s.insert((Dat){r+1,t.r,tn,1}); } }
it就是我们当前要分裂的区间,(*it).nd就是这个区间的根节点,l,r是我们这次操作的区间。对于Dat这个结构体我们采取右端点优先,左端点次一级的排序。
由于升降序不同,我们要从要分裂的区间的线段树取出来的部分也不同,可能是后面的,也可能是前面的,这就需要分类讨论一下了(不要怕麻烦,懒得话就不要学这种做法)
还学到了一个回收空间内存的做法,具体时间就是用一个栈保存一下可以使用的结点,每次要用结点的时候让一个元素出栈即可,可见getnode函数
#include<algorithm> #include<cstring> #include<iostream> #include<cstdio> #include<set> using namespace std; int n,m; namespace Seg { const int N=6e5+15; int lx[N],rx[N],siz[N]; int st[N+10];int top; void init() {for (int i=N-1;i>=1;i--) st[++top]=i;} int getnode() {int t=st[top--];lx[t]=rx[t]=siz[t]=0;return t;} void delnode(int x) {st[++top]=x;} #define mid ((l+r)>>1) void update(int &o,int l,int r,int x) { if (!o) o=getnode(); if (l==r) {siz[o]++;return;} if (x<=mid) update(lx[o],l,mid,x); else update(rx[o],mid+1,r,x); siz[o]=siz[lx[o]]+siz[rx[o]]; } void split(int l,int r,int o,int &x,int k) { if (!k) return; if (!x) x=getnode(); if (l==r) {siz[o]-=k;siz[x]+=k;return;} int t=siz[lx[o]]; if (t>k) split(l,mid,lx[o],lx[x],k); if (t==k) lx[x]=lx[o],lx[o]=0; if (t<k) { lx[x]=lx[o];lx[o]=0; split(mid+1,r,rx[o],rx[x],k-t); } siz[o]=siz[lx[o]]+siz[rx[o]]; siz[x]=siz[lx[x]]+siz[rx[x]]; } int merge(int x,int y) { if (!x||!y) return x|y; lx[x]=merge(lx[x],lx[y]); rx[x]=merge(rx[x],rx[y]); siz[x]+=siz[y]; delnode(y); return x; } int kth(int l,int r,int o,int k) { if (l==r) return l; if (siz[lx[o]]>=k) return kth(l,mid,lx[o],k); else return kth(mid+1,r,rx[o],k-siz[lx[o]]); } } struct Dat { int l,r,nd,type; }; bool operator <(const Dat &a,const Dat &b) { return a.r<b.r||(a.r==b.r&&a.l<b.l); } set <Dat> s; inline int read() { char ch=getchar(); int s=0,f=1; while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } int split_node(int l,int r) { set<Dat>::iterator it=s.lower_bound((Dat){0,l,0,0}); if ((*it).l!=l) { Dat t=*it;s.erase(it);int tn=0; if (t.type==0) { Seg::split(1,n,t.nd,tn,l-t.l); s.insert((Dat){t.l,l-1,tn,0}); s.insert((Dat){l,t.r,t.nd,0}); //printf("qq%d %d\n",t.l,l-1); //printf("qq%d %d\n",l,t.r); } else { Seg::split(1,n,t.nd,tn,t.r-l+1); s.insert((Dat){t.l,l-1,t.nd,1}); s.insert((Dat){l,t.r,tn,1}); } } it=s.lower_bound((Dat){0,r,0,0}); if ((*it).r!=r) { Dat t=*it;s.erase(it);int tn=0; if (t.type==0) { Seg::split(1,n,t.nd,tn,r-t.l+1); //printf("qq%d\n",tn); s.insert((Dat){t.l,r,tn,0}); s.insert((Dat){r+1,t.r,t.nd,0}); } else { Seg::split(1,n,t.nd,tn,t.r-r); s.insert((Dat){t.l,r,t.nd,1}); s.insert((Dat){r+1,t.r,tn,1}); } } int re=0; while (1) { it=s.lower_bound((Dat){0,l,0,0}); if (it==s.end()||(*it).l>r) break; Dat t=(*it);s.erase(it); re=Seg::merge(re,t.nd); } //printf("%d\n",re); return re; } int main() { Seg::init(); n=read();m=read(); for (int i=1;i<=n;i++) { int t=0; Seg::update(t,1,n,read()); //printf("%d\n",t); s.insert((Dat){i,i,t,0}); } while (m--) { int op=read(),l=read(),r=read(); int t=split_node(l,r); s.insert((Dat){l,r,t,op}); } int q=read(); int t=split_node(q,q); //printf("%d\n",t); printf("%d\n",Seg::kth(1,n,t,1)); return 0; }