「HEOI2016/TJOI2016」排序
「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」排序的更多相关文章
- 「HEOI2016/TJOI2016」 排序
题目链接 戳我 \(Solution\) 这道题在线的做法不会,所以这里就只讲离线的做法. 因为直接排序的话复杂度显然不对.但是如果数列为\(01\)串的话就可以让复杂度变成对的了 那么\(01\)串 ...
- 「HEOI2016/TJOI2016」序列
题目链接 戳这 Solution 首先考虑最暴力的dp 我们设: \(f[i]\)表示选择\(i\)以后所能形成的满足条件的子序列的最大值 \(minx[i]\)表示\(i\)能转换为的最小值 \(m ...
- loj #2055. 「TJOI / HEOI2016」排序
#2055. 「TJOI / HEOI2016」排序 题目描述 在 2016 年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他. 这个 ...
- 洛谷 P2824 [HEOI2016/TJOI2016]排序 解题报告
P2824 [HEOI2016/TJOI2016]排序 题意: 有一个长度为\(n\)的1-n的排列\(m\)次操作 \((0,l,r)\)表示序列从\(l\)到\(r\)降序 \((1,l,r)\) ...
- LibreOJ2241 - 「CQOI2014」排序机械臂
Portal Description 给出一个\(n(n\leq10^5)\)个数的序列\(\{a_n\}\),对该序列进行\(n\)次操作.若在第\(i\)次操作前第\(i\)小的数在\(p_i\) ...
- [HEOI2016/TJOI2016]排序 线段树+二分
[HEOI2016/TJOI2016]排序 内存限制:256 MiB 时间限制:6000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而 ...
- [Luogu P2824] [HEOI2016/TJOI2016]排序 (线段树+二分答案)
题面 传送门:https://www.luogu.org/problemnew/show/P2824 Solution 这题极其巧妙. 首先,如果直接做m次排序,显然会T得起飞. 注意一点:我们只需要 ...
- 2021.12.09 [HEOI2016/TJOI2016]排序(线段树+二分,把一个序列转换为01串)
2021.12.09 [HEOI2016/TJOI2016]排序(线段树+二分,把一个序列转换为01串) https://www.luogu.com.cn/problem/P2824 题意: 在 20 ...
- fir.im Weekly - 如何打造 Github 「爆款」开源项目
最近 Android 转用 Swift 的传闻甚嚣尘上,Swift 的 Github 主页上已经有了一次 merge>>「Port to Android」,让我们对 Swift 的想象又多 ...
随机推荐
- FinFET与2nm晶圆工艺壁垒
FinFET与2nm晶圆工艺壁垒 谈到半导体工艺尺寸的时候,通常对于下面的一串数字耳熟能详:3um.2um.1.5um.1um.0.8um.0.5um.0.35um.0.25um.0.18um.0.1 ...
- 细粒度语义分割:ICCV2019论文解析
细粒度语义分割:ICCV2019论文解析 Fine-Grained Segmentation Networks: Self-Supervised Segmentation for Improved L ...
- Git学习笔记(快速上手)
Git学习 1. 基本使用 安装成功后在开始菜单中会有Git项,菜单下有3个程序:任意文件夹下右键也可以看到对应的程序! Git Bash:Unix与Linux风格的命令行,使用最多,推荐最多 Git ...
- HashMap源码解析和设计解读
HashMap源码解析 想要理解HashMap底层数据的存储形式,底层原理,最好的形式就是读它的源码,但是说实话,源码的注释说明全是英文,英文不是非常好的朋友读起来真的非常吃力,我基本上看了差不多 ...
- python operator操作符函数
本模块主要包括一些Python内部操作符对应的函数.这些函数主要分为几类:对象比较.逻辑比较.算术运算和序列操作.
- 你能说出多线程中sleep、yield、join的用法及sleep与wait区别?
Object中的wait.notify.notifyAll,可以用于线程间的通信,核心原理为借助于监视器的入口集与等待集逻辑 通过这三个方法完成线程在指定锁(监视器)上的等待与唤醒,这三个方法是以锁( ...
- 『无为则无心』Python基础 — 4、Python代码常用调试工具
目录 1.Python的交互模式 2.IDLE工具使用说明 3.Sublime3工具的安装与配置 (1)Sublime3的安装 (2)Sublime3的配置 4.使用Sublime编写并调试Pytho ...
- 关于Linux服务器部署
服务器信息: 此小节的内容: SecurityCRT:用来连接到Linux服务器命令操作. FTP(FTPRush):本地文件和Linux服务器文件交互的 工具服务器 借助客户端工具来链接到Linux ...
- 题解 P5318 【【深基18.例3】查找文献】
题目传送门 根据本蒟蒻细致粗略的分析 这明显是一道水题模(du)板(liu)题 可我竟然没有一遍AC; 为更好地食用本题解需要了解以下内容 1.dfs(大法师深搜) 2.bfs(冰法师广搜)/dij最 ...
- CSS 奇思妙想 | 全兼容的毛玻璃效果
通过本文,你能了解到 最基本的使用 CSS backdrop-filter 实现磨砂玻璃(毛玻璃)的效果 在至今不兼容 backdrop-filter 的 firefox 浏览器,如何利用一些技巧性的 ...