「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. sql批量插入缓慢

    1.有一个普通的表t_asset,只有2个字段id,ip 没有索引 2.当用insert into t_asset(id,ip) values(?,?),(?,?) 1200多条记录时,发现竟然用了3 ...

  2. 情景剧:C/C++中的未定义行为(undefined behavior)

    写在前面 本文尝试以情景剧的方式,轻松.直观地解释C/C++中未定义行为(undefined behavior)的概念.设计动机.优缺点等内容1,希望读者能够通过阅读本文,对undefined beh ...

  3. Paddle Release Note

    Paddle Release Note 重要更新 飞桨paddle框架2.0.0版本有如下重要更新: 编程范式:默认开启动态图模式进行模型开发和训练,通过动转静的方式进行模型部署和训练加速.如果需要使 ...

  4. Docker_Swarm集群系统

    Docker_Swarm集群系统 一.Docker Swarm 介绍 实践中会发现,生产环境中使用单个 Docker 节点是远远不够的,搭建 Docker 集群势在必行.然而,面对 Kubernete ...

  5. Druid数据库连接池基本使用

    一.导入Druid的jar包和数据库驱动jar包 二.定义配置文件 与c3p0不同,Druid的配置文件是properties形式的.而且Druid不像c3p0那样可以自动加载配置文件,Druid需要 ...

  6. QT Dialog模态与非模态

    模态 // 创建对话框窗口 TestDialog* dlg = new TestDialog(this); // 阻塞程序的运行 dlg->exec(); 这样的话,当运行对话窗口的时候,会阻塞 ...

  7. Redis的过期键删除策略

    文章首发于公众号:蘑菇睡不着,欢迎来看看 前言 Redis 中都是键值对的存储形式,键都是字符串类型的,而值有很多种类型,如 string.list.hash.set.sorted set等类型.当设 ...

  8. docker安装及卸载

    docker基本组成 镜像(image): docker镜像好比一个模板,可以通过这个模板创建容器服务,例如:tomcat镜像===>run===>tomcat01容器(提供服务器) 通过 ...

  9. 通过Z-Order技术加速Hudi大规模数据集分析方案

    1. 背景 多维分析是大数据分析的一个典型场景,这种分析一般带有过滤条件.对于此类查询,尤其是在高基字段的过滤查询,理论上只我们对原始数据做合理的布局,结合相关过滤条件,查询引擎可以过滤掉大量不相关数 ...

  10. Django(66)admin后台管理注册用户

    前言 我们使用django创建用户可以使用注册接口的方式,也可以使用django自带的后台管理系统,这里就介绍使用后台管理系统创建用户 admin后台管理系统 在使用之前我们可以使用第三方的插件,来美 ...