题目链接:

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){,l,,});
if ((*it).l!=l)
{
Dat t=*it;s.erase(it);int tn=;
if (t.type==)
{
Seg::split(,n,t.nd,tn,l-t.l);
s.insert((Dat){t.l,l-,tn,});
s.insert((Dat){l,t.r,t.nd,});
//printf("qq%d %d\n",t.l,l-1);
//printf("qq%d %d\n",l,t.r);
}
else
{
Seg::split(,n,t.nd,tn,t.r-l+);
s.insert((Dat){t.l,l-,t.nd,});
s.insert((Dat){l,t.r,tn,});
}
}
it=s.lower_bound((Dat){,r,,});
if ((*it).r!=r)
{
Dat t=*it;s.erase(it);int tn=;
if (t.type==)
{
Seg::split(,n,t.nd,tn,r-t.l+);
//printf("qq%d\n",tn);
s.insert((Dat){t.l,r,tn,});
s.insert((Dat){r+,t.r,t.nd,});
}
else
{
Seg::split(,n,t.nd,tn,t.r-r);
s.insert((Dat){t.l,r,t.nd,});
s.insert((Dat){r+,t.r,tn,});
}
}

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+;
int lx[N],rx[N],siz[N];
int st[N+];int top;
void init() {for (int i=N-;i>=;i--) st[++top]=i;}
int getnode() {int t=st[top--];lx[t]=rx[t]=siz[t]=;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+,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]=;
if (t<k)
{
lx[x]=lx[o];lx[o]=;
split(mid+,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+,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=,f=;
while (ch<''||ch>'') {if (ch=='-') f=-;ch=getchar();}
while (ch>=''&&ch<='') {s=(s<<)+(s<<)+ch-'';ch=getchar();}
return s*f;
}
int split_node(int l,int r)
{
set<Dat>::iterator it=s.lower_bound((Dat){,l,,});
if ((*it).l!=l)
{
Dat t=*it;s.erase(it);int tn=;
if (t.type==)
{
Seg::split(,n,t.nd,tn,l-t.l);
s.insert((Dat){t.l,l-,tn,});
s.insert((Dat){l,t.r,t.nd,});
//printf("qq%d %d\n",t.l,l-1);
//printf("qq%d %d\n",l,t.r);
}
else
{
Seg::split(,n,t.nd,tn,t.r-l+);
s.insert((Dat){t.l,l-,t.nd,});
s.insert((Dat){l,t.r,tn,});
}
}
it=s.lower_bound((Dat){,r,,});
if ((*it).r!=r)
{
Dat t=*it;s.erase(it);int tn=;
if (t.type==)
{
Seg::split(,n,t.nd,tn,r-t.l+);
//printf("qq%d\n",tn);
s.insert((Dat){t.l,r,tn,});
s.insert((Dat){r+,t.r,t.nd,});
}
else
{
Seg::split(,n,t.nd,tn,t.r-r);
s.insert((Dat){t.l,r,t.nd,});
s.insert((Dat){r+,t.r,tn,});
}
}
int re=;
while ()
{
it=s.lower_bound((Dat){,l,,});
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=;i<=n;i++)
{
int t=;
Seg::update(t,,n,read());
//printf("%d\n",t);
s.insert((Dat){i,i,t,});
}
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(,n,t,));
return ;
}

[HEOI2016/TJOI2016] 排序 解题报告(二分答案/线段树分裂合并+set)的更多相关文章

  1. 洛谷 P2824 [HEOI2016/TJOI2016]排序 解题报告

    P2824 [HEOI2016/TJOI2016]排序 题意: 有一个长度为\(n\)的1-n的排列\(m\)次操作 \((0,l,r)\)表示序列从\(l\)到\(r\)降序 \((1,l,r)\) ...

  2. [BZOJ4552][TJOI2016&&HEOI2016]排序(二分答案+线段树/线段树分裂与合并)

    解法一:二分答案+线段树 首先我们知道,对于一个01序列排序,用线段树维护的话可以做到单次排序复杂度仅为log级别. 这道题只有一个询问,所以离线没有意义,而一个询问让我们很自然的想到二分答案.先二分 ...

  3. Codeforces Round #276 (Div. 1) E. Sign on Fence (二分答案 主席树 区间合并)

    链接:http://codeforces.com/contest/484/problem/E 题意: 给你n个数的,每个数代表高度: 再给出m个询问,每次询问[l,r]区间内连续w个数的最大的最小值: ...

  4. bzoj 4552 [Tjoi2016&Heoi2016]排序 (二分答案 线段树)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4552 题意: 给你一个1-n的全排列,m次操作,操作由两种:1.将[l,r]升序排序,2 ...

  5. BZOJ 4552 [Tjoi2016&Heoi2016]排序 | 二分答案 线段树

    题目链接 题面 题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这 ...

  6. [BZOJ4552][Tjoi2016&Heoi2016]排序(二分答案+线段树)

    二分答案mid,将>=mid的设为1,<mid的设为0,这样排序就变成了区间修改的操作,维护一下区间和即可 然后询问第q个位置的值,为1说明>=mid,以上 时间复杂度O(nlog2 ...

  7. 【BZOJ4552】【HEOI2016】排序 [二分答案][线段树]

    排序 Time Limit: 60 Sec  Memory Limit: 256 MB[Submit][Status][Discuss] Description 在2016年,佳媛姐姐喜欢上了数字序列 ...

  8. 【Luogu】P2824排序(二分答案+线段树排序)

    题目链接 震惊!两个线段树和一个线段树竟是50分的差距! 本题可以使用二分答案,二分那个位置上最后是什么数.怎么验证呢? 把原序列改变,大于等于mid的全部变成1,小于mid的全部变成0,之后线段树排 ...

  9. BZOJ4556 [Tjoi2016&Heoi2016]字符串 SA ST表 二分答案 主席树

    原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ4556.html 题目传送门 - BZOJ4556 题意 给定一个长度为 $n$ 的字符串 $s$ . ...

随机推荐

  1. c语言数组小谈

    #include <stdio.h> #include <stdlib.h> #define N 5 int main() { double score[5]; int i; ...

  2. XCode下Swift – WebView IOS demo

    简介 我今天用Mac升级了XCode到8.1,Swift版本应该到了swift3,按网上的demo写webview的例子,报一堆错,整了一天才搞定,不想其他人踩坑了! XCode8.1 ,swift3 ...

  3. oracle 11g rac 修改VIP、scan VIP、priv IP

    11GR2 RAC modify vip,public ip,private ip,scan vip实施步骤1 修改目的    根据业务的需求,需要由原来的临时IP改为生产ip,以下为调整前后对应的I ...

  4. 没有被广泛采用的box-sizing属性

    在标准盒模型下设置的width和height只是内容的宽和高,但在设置了宽和高的情况下若还要设置border.margin.padding等时,会发生溢出的现象,因此需要将盒模型更改. box-siz ...

  5. 36.QT地图

    widget.h #ifndef MAPWIDGET_H #define MAPWIDGET_H #include <QGraphicsView> #include <QLabel& ...

  6. MyEclipse 安装svn 插件步骤详情

    方法一:在线安装 打开HELP- > MyEclipse Configuration Center.切换到SoftWare标签页. 点击Add Site 打开对话框,在对话框Name输入Svn, ...

  7. Android 多个APK共享数据

    Android给每个APK进程分配一个单独的用户空间,其manifest中的userid就是对应一个Linux用户(Android 系统是基于Linux)的.所以不同APK(用户)间互相访问数据默认是 ...

  8. Android FloatMenuSample

    项目地址:fanOfDemo/FloatMenuSample crosg/FloatMenuSample transfer from yiming/FloatMenuSample GIF GRADLE ...

  9. 02操控奴隶——掌握它的语言“Python”

    一 编程常识 1编程语言的发展史 程序员是计算机的主人,主人与奴隶沟通的介质是编程语言,编程语言从诞生到现在它经历了那几个阶段呢? 2 语言的特性: 3 初期的编程语言更多的是站在计算机的角度去设计编 ...

  10. day 06 数据类型的内置方法[数字类型,字符串类型]

    什么是可变还是不可变? 可变是值可以改变,但是ID不变,不可变是值变,ID也变. 1.数字类型 int 1.用途:年龄,号码,等级 2.定义:可以使用int()方法将纯数字的字符串转为十进制的整数 a ...