「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. 小程序中在设置了textarea后三个祖级内事件失效

    在一次写小程序项目中收货地址中的详细地址时,我用的是文本域,下边的三个bindtap事件却不能使用了:下图: 报错信息如下图: 通过一番查找以及尝试之后,我发现是因为textarea标签的问题,但是依 ...

  2. MongoDB学习笔记:MongoDB 数据库的命名、设计规范

    MongoDB学习笔记:MongoDB 数据库的命名.设计规范     第一部分,我们先说命名规范. 文档 设计约束 UTF-8 字符 不能包含 \0 字符(空字符),这个字符标识建的结尾 . 和 $ ...

  3. Python+Selenium学习笔记8 - 多表单&多窗口切换

    1.多表单切换 下图为待测页面:内嵌百度首页 切换到百度首页进行操作 1 # coding = utf-8 2 3 from selenium import webdriver 4 import os ...

  4. 错误码:events.js:183 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE :::8081

    错误的产生: 错误的原因: 端口被占用 修改访问端口就可以了 https://blog.csdn.net/qq_25479327/article/details/79824742

  5. 智能物联网(AIoT,2020年)(上)

    智能物联网(AIoT,2020年)(上) 中国AloT的概念与现状 01智能物联网(AIoT)定义 人工智能与物联网的协同应用 02 AIoT2025产业瞭望:家庭AI管家 智能家居交互方式无感化,跨 ...

  6. MindSpore API编程概述

    MindSpore API编程概述 总体架构 MindSpore是一个全场景深度学习框架,旨在实现易开发.高效执行.全场景覆盖三大目标,其中易开发表现为API友好.调试难度低,高效执行包括计算效率.数 ...

  7. 英特尔内存革新助平安云 Redis 云服务降本增效

    英特尔内存革新助平安云 Redis 云服务降本增效 英特尔 傲腾 数据中心级持久内存的引入,为平安云的降本增效开启了一条新的道路.通过对平安云 Redis 数据库产品的支持,用户能享受到性能优异且价格 ...

  8. RTOS诊断和错误检查

    RTOS诊断和错误检查 RTOS diagnostics and error checking 查看RTOS显示系列 错误处理不太可能是任何用于嵌入式系统应用程序的操作系统的主要功能.这是资源限制的必 ...

  9. python_appium 之使用Appium Inspector定位工具进行元素识别,编写验证demo

    一.前提条件 appium环境搭建完成,模拟器Genymotion 安装完成,且已经下载成功了模拟设备 二.元素识别操作步骤及demo 1.进入appium如下菜单 2.填写Desired Capab ...

  10. python+selenium_鼠标事件

    引言--在实际的web产品测试中,对于鼠标的操作,不单单只有click(),有时候还要用到右击.双击.拖动等操作,这些操作包含在ActionChains类中. 一.ActionChains类中鼠标操作 ...