「HEOI2016/TJOI2016」排序

题目大意

给定一个 \(1\) 到 \(n\) 的排列,每次可以对这个序列的一个区间进行升序/降序排序,求所有操作后第 \(q\) 个位置上的数字。

题解

大棒子,又学到了许多。

做法很多,这里大概讲一下主流的几种做法。

在线做法

  • 线段树合并&分裂

其实将一个区间升序或降序排序可以看作同一个操作——进行升序排序,打一个是否是升序排序的标记。

所以我们可以在每一个位置维护一棵权值线段树,当要将区间 \([l,r]\) 的数字排序时,取出这些位置所维护的权值线段树合并至某一特定位置即可。

但是我们不可能每次合并过后又暴力将其分裂回每个位置上,这样复杂度肯定是吃不消的。所以我们考虑每一次在上一次的基础上取出我们想要的区间进行合并。

我们假设合并时将线段树合并至区间的左端点。

我们可以维护一个 \(\texttt{set}\),代表现存的线段树左端点标号。

我们在 \(\texttt{set}\) 中去二分找到包含端点的那棵权值线段树,然后根据升序/降序的标记做一个分类讨论。

设这个端点为 \(k\)。

升序:将这棵权值线段树分裂成 \([l,k-1]\) 和 \([k,r]\) 两个部分,则这个端点所维护的线段树区间为 \([k,r]\)。

降序:将这棵权值线段树分裂成 \([l,r-k-1]\) 和 \([r-k,r]\) 两个部分,则这个端点所维护的线段树区间为 \([l,r-k-1]\)。这里一定要注意因为是降序所以处理有一定不同。

记得要分裂出来的两棵线段树标记须与原来的那棵线段树保持一致。

时间复杂度为 \(O((n+m)\log_2n)\)

代码真的很可读,就没啥注释了OWO。

/*---Author:HenryHuang---*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int son[maxn*30][2],rs[maxn*30],bac[maxn*30],sum[maxn*30],cnt,tot;
int rt[maxn],tag[maxn];
int newnode(){
return cnt?bac[cnt--]:++tot;
}
void del(int u){
bac[++cnt]=u,son[u][0]=son[u][1]=sum[u]=0;
}
void update(int &u,int l,int r,int pos,int val){
if(!u) u=newnode();sum[u]+=val;
if(l==r) return ;int mid=(l+r)>>1;
if(pos<=mid) update(son[u][0],l,mid,pos,val);
else update(son[u][1],mid+1,r,pos,val);
return ;
}
void split(int x,int &y,int k){
y=newnode();
int v=sum[son[x][0]];
if(k>v) split(son[x][1],son[y][1],k-v);
else swap(son[x][1],son[y][1]);
if(k<v) split(son[x][0],son[y][0],k);
sum[y]=sum[x]-k,sum[x]=k;
return ;
}
int merge(int x,int y){
if(!x||!y) return x+y;
sum[x]+=sum[y];
son[x][0]=merge(son[x][0],son[y][0]);
son[x][1]=merge(son[x][1],son[y][1]);
del(y);
return x;
}
set<int> s;
set<int> ::iterator id(int x){
set<int> ::iterator it=s.lower_bound(x);
if(*it==x) return it;
int p=*it;//这就是r+1
int l=*--it;//这是l
if(!tag[l]) split(rt[l],rt[x],x-l),tag[x]=tag[l]=0;
else{
split(rt[l],rt[x],p-x),swap(rt[l],rt[x]),tag[l]=tag[x]=1;
}
return s.insert(x).first;
}
void solve(int l,int r){
set<int> ::iterator L=id(l),R=id(r+1);
for(set<int> ::iterator it=++L;it!=R;++it) rt[l]=merge(rt[l],rt[*it]);//合并
s.erase(L,R);//全部删掉,因为都合并掉了
}
int query(int u,int l,int r){
if(l==r) return l;
int mid=(l+r)>>1;
if(son[u][0]) return query(son[u][0],l,mid);
else return query(son[u][1],mid+1,r);
}//最后查答案
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n,m;cin>>n>>m;
s.insert(n+1);
for(int i=1;i<=n;++i){
int x;cin>>x;
update(rt[i],1,n,x,1);
s.insert(i);
}
for(int i=1;i<=m;++i){
int opt,l,r;
cin>>opt>>l>>r;
solve(l,r);tag[l]=opt;
}
int q;cin>>q;
id(q),id(q+1);
cout<<query(rt[q],1,n)<<'\n';
return 0;
}
  • 平衡树启发式合并&分裂

同理,只是将线段树换为 Splay/FHQ Treap。个人推荐FHQ Treap。(因为你不用额外徒增代码量,似乎复杂度也更对)

离线做法

  • 线段树+二分

如果对于一个排列,我们进行区间排序显然是不好维护的。

那么对于一个 \(\texttt{0/1}\) 序列呢?

我们只需要进行统计 \(0,1\) 的个数,然后进行区间赋值即可。

所以我们的问题就变成了区间赋值,区间求和

这个用一棵线段树可以直接维护。

所以我们考虑二分答案 \(x\),将 \(\ge x\) 的数设为 \(1\),其他数设为 \(0\)。

我们所需要的答案就是最后答案位置上的数字为 \(1\) 时的最大的 \(x\)。

最后的时间复杂度为 \(O(m\log_2^2n)\)。

「HEOI2016/TJOI2016」排序的更多相关文章

  1. 「HEOI2016/TJOI2016」 排序

    题目链接 戳我 \(Solution\) 这道题在线的做法不会,所以这里就只讲离线的做法. 因为直接排序的话复杂度显然不对.但是如果数列为\(01\)串的话就可以让复杂度变成对的了 那么\(01\)串 ...

  2. 「HEOI2016/TJOI2016」序列

    题目链接 戳这 Solution 首先考虑最暴力的dp 我们设: \(f[i]\)表示选择\(i\)以后所能形成的满足条件的子序列的最大值 \(minx[i]\)表示\(i\)能转换为的最小值 \(m ...

  3. loj #2055. 「TJOI / HEOI2016」排序

    #2055. 「TJOI / HEOI2016」排序   题目描述 在 2016 年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他. 这个 ...

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

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

  5. LibreOJ2241 - 「CQOI2014」排序机械臂

    Portal Description 给出一个\(n(n\leq10^5)\)个数的序列\(\{a_n\}\),对该序列进行\(n\)次操作.若在第\(i\)次操作前第\(i\)小的数在\(p_i\) ...

  6. [HEOI2016/TJOI2016]排序 线段树+二分

    [HEOI2016/TJOI2016]排序 内存限制:256 MiB 时间限制:6000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而 ...

  7. [Luogu P2824] [HEOI2016/TJOI2016]排序 (线段树+二分答案)

    题面 传送门:https://www.luogu.org/problemnew/show/P2824 Solution 这题极其巧妙. 首先,如果直接做m次排序,显然会T得起飞. 注意一点:我们只需要 ...

  8. 2021.12.09 [HEOI2016/TJOI2016]排序(线段树+二分,把一个序列转换为01串)

    2021.12.09 [HEOI2016/TJOI2016]排序(线段树+二分,把一个序列转换为01串) https://www.luogu.com.cn/problem/P2824 题意: 在 20 ...

  9. fir.im Weekly - 如何打造 Github 「爆款」开源项目

    最近 Android 转用 Swift 的传闻甚嚣尘上,Swift 的 Github 主页上已经有了一次 merge>>「Port to Android」,让我们对 Swift 的想象又多 ...

随机推荐

  1. [ Java面试题 ]Java 开发岗面试知识点解析

    如背景中介绍,作者在一年之内参加过多场面试,应聘岗位均为 Java 开发方向. 在不断的面试中,分类总结了 Java 开发岗位面试中的一些知识点. 主要包括以下几个部分: Java 基础知识点 Jav ...

  2. Python+Selenium学习笔记5 - python官网的tutorial - 交互模式下的操作

    这篇笔记主要是从Python官网的Tutorial上截取下来,再加上个人理解 1. 在交互模式下,下划线'_'还可以表示上一步的计算结果 2.引号转义问题. 从下图总结的规律是,字符串里的引号如果和引 ...

  3. CUDA 8混合精度编程

    CUDA 8混合精度编程 Mixed-Precision Programming with CUDA 8 论文地址:https://devblogs.nvidia.com/mixed-precisio ...

  4. 开发掉坑(二)前端静态资源 Uncaught SyntaxError: Unexpected token <

    某天,有同学反馈后台管理系统出现静态资源无法加载的问题. 复现如下: 进入首页. 点击侧边栏某个子功能,静态资源可正常访问到. 等待10分钟左右,点击侧边栏其他子功能,无法访问到静态资源. 查看控制台 ...

  5. java.lang.ClassNotFoundException: org.apache.curator.RetryPolicy

    dubbo项目启动过程中遇到这个异常,很明显是找不到curator的包,所以需要引入curator的相关包才可以, curator是zookeeper的客户端框架,且要引入完整,才不会报错 比如: & ...

  6. 面试官:给我讲讲SpringBoot的依赖管理和自动配置?

    1.前言 从Spring转到SpringBoot的xdm应该都有这个感受,以前整合Spring + MyBatis + SpringMVC我们需要写一大堆的配置文件,堪称配置文件地狱,我们还要在pom ...

  7. Boost 矩形布尔运算

    本文主要介绍boost库中矩形布尔运算的思路和代码解析.在此感谢Intel对开源事业的贡献. 基本概念 图形的插入 所有的操作的第一步都是进行图形的插入,我们需要声明定义好我们要使用的图形类型,以矩形 ...

  8. 史上最详细的Air7xx驱动安装教程

    由于Air7xx系列4G模块需要安装USB驱动,但是很多开发者对USB驱动的安装方法不是十分了解,所以经常出现问题,导致安装失败.特书此文,手把手教你装USB驱动. 第一步 从官网下载最新的驱动程序 ...

  9. dos脚本语法学习

    一个dos批处理脚本,通过关键字搜索注册表并删除,坑很多,语法也很怪异,详情看注释 @echo off ::声明采用UTF-8编码,避免中文乱码问题,>NUL可以吞掉chcp输出的内容 chcp ...

  10. ES7扩展

    前一段时间小编一直在更新javascript es6版本的部分新语法和新特性,鉴于现在js一直在更新,接下来小编将和大家一起进步,一块探究js的新特性.今天小编就和大家一起来看看es7更新的语法和新特 ...