「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)\)。

最新文章

  1. [转]ASP.NET Core 之 Identity 入门(二)
  2. SQL Server服务器上需要导入Excel数据的必要条件
  3. webapi支持跨域访问
  4. MongoDB安装并设置为windows服务以使其开机自启
  5. 深入理解MySQL开发性能优化.pptx
  6. 在Android中使用并发来提高速度和性能
  7. OrderedDict
  8. php图片上传
  9. IOS学习教程
  10. 使用JLink间接烧写S3C2410、S3C2440开发板Nor、Nand Flash的方法
  11. 【jQueryMobile】Helloworld而页面切换
  12. JavaScript shift() 方法
  13. 201521123008《Java程序设计》第八周实验总结
  14. SpringMvc开发步骤
  15. Android View框架总结(八)ViewGroup事件分发机制
  16. android------个人项目(歆语气象通新版)
  17. Python 多线程的程序不结束多进程的程序不结束的区别
  18. PHP文件PHP代码及运行(适合PHP初学者)
  19. linux-2.6.26内核中ARM中断实现详解(转)
  20. 2017-2018 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) M - Unsatisfying 2-Sat

热门文章

  1. 实现不同VLAN间的通信(单臂路由和链路聚合)
  2. Linux系统挂载NFS文件系统
  3. Django优雅集成MongoDB
  4. GPU加速库AmgX
  5. 微调torchvision 0.3的目标检测模型
  6. httprunner_安装及利用脚手架工具快速创建项目
  7. maven 安装、下载、配置,idea中的maven设置
  8. 给元素设置overflow:hidden,pc端正常,但移动端依然可以滑动
  9. LockSupport中的park()与unpark()
  10. 【题解】Luogu P2875 [USACO07FEB]牛的词汇The Cow Lexicon