题解:P8747 [蓝桥杯 2021 省 B] 双向排序
能用珂朵莉写的谁用线段树啊
:::align{right}
——周树根
:::
提供均摊 \(O((n + m) \log m)\) 类珂朵莉树写法。
一、题目概述
本题给定初始序列 \([1 \dots n]\),进行 m 次排序操作:
- \(p = 0\):将前 \(q\) 个降序
- \(p = 1\):将从 \(q\) 到 \(n\) 升序
传统思路如果每次直接排序,单次 \(O(n \log n)\),总复杂度 \(O(nm \log n)\),\(n,m\) 都是 \(10^5\),不可行。
本题最关键的突破思路:排序不是交换,而是重新写入顺序,覆盖掉原来的结构。
该问题最终解法的核心思想不是排序,而是区间覆盖与单调片段维护。
二、核心现象:排序等价于“覆盖”
最终数组不是由一次次交换产生,而是由一次次覆盖构成
并且最终一定表现为一条折线序列:
降序区 + 降序区 + ... + 升序区 + 升序区
每一段区间:
- 单调(升/降)
- 数值连续(因为值来自排序覆盖,不跳跃)
最低点向两边的每段区间:
- 值域单调
- 数值不重
如:
5 4 1 2 3
\ |
\ |
| /
| /
\__|
任意取一条水平线交于两点,这两点间所有数的集合是连续的。
这就是“区间模型”的理论基础。
三、图例解释:区间如何构造与覆盖
以 \(n = 10\) 举例。
10 2
0 5
1 4
初始状态
位置: 1 2 3 4 5 6 7 8 9 10
值 : 1 2 3 4 5 6 7 8 9 10
(1 1→10 UP)
即从位置 \(1\) 开始,数字为 \(1\) 到 \(10\),方向 UP(升序)。
操作①:\(p = 0, q = 5\)(前 \(5\) 排降序)
覆盖 \([1 \dots 5]\)。
位置: 1 2 3 4 5 | 6 7 8 9 10
值 : 5 4 3 2 1 | 6 7 8 9 10
区间:(1 5→1 DOWN)+ (6 6→10 UP)
操作②:\(p = 1, q = 4\)(后 \(7\) 排升序)
覆盖 \([4 \dots 10]\),但这个区域跨越了“降序区 + 升序区”,找到 \([4 \dots 5]\):
| / | /
\ | \ |
_\___|______\___|__
\__| |__/
覆盖前包含的值:
位置 1 2 3 4 5 6 7 8 9 10
旧值 5 4 3 2 1 6 7 8 9 10
排序后 5 4 3 1 2 6 7 8 9 10
(1 5→3 DOWN) + (4 1→2 UP) + (6 6→10 UP)
视觉比喻:像是用颜色覆盖表面的色块
原区块: █████□□□□□
现区块: ██》》》□□□□□
四、本题思考框架
每一个操作:
- 找到对应点
- 删除被覆盖的区间
- 添加一个新单调区间
因此最终只需维护:
- 若干按位置排序的区间
- 区间包含值范围 + 方向
通过 set 查区间,按方向计算,不需要真实数组存储。
其实就是珂朵莉 233。
整体复杂度 \(O((n + m) \log m)\)。
五、输出结果
最后按区间顺序输出值:
| 如果是 UP | vl → vr 递增输出 |
| 如果是 DOWN | vr → vl 递减输出 |
六、思路总结
本题本质不是排序问题,而是区间覆盖问题。
- 排序会掩盖旧信息,因此无需保存旧数组
- 每次形成一段连续单调区间
- 利用有序集合维护区间边界
- 区间最大规模 \(\le\) 操作次数 \(m\)
- 最终输出 \(=\) 遍历区间并按单调方向输出
七、代码
#include<bits/stdc++.h>
#define si set<node>::iterator
using namespace std;
struct node{
int pre;
int vl,vr;
int isup;
bool operator <(const node &b) const{
return pre <b.pre;
}
node(int P=0,int L=0,int R=0,int U=0):pre(P),vl(L),vr(R),isup(U){}
};
set<node> cht;
int first_up=1;
int n,m;
int getval(int x){
si it=cht.lower_bound(node(x));
if(it->isup) return it->vr-it->pre+x;
else return it->vl+it->pre-x;
}
void split(int newl){
si it=cht.lower_bound(node(newl));
int val=getval(newl);
if((--it)->pre==newl-1) return;
it++;
int p=it->pre,l=it->vl,r=it->vr,f=it->isup;
cht.erase(it);
if(f){
cht.insert(node(newl-1,l,val-1,1));
cht.insert(node(p,val,r,1));
}
else{
cht.insert(node(newl-1,val+1,r,0));
cht.insert(node(p,l,val,0));
}
return;
}
void assign(int maxv,int newup){
vector<node> dels;
int maxd=0,nf=-1;
for(si F=cht.lower_bound(node(first_up));F!=cht.end();F++){
if(F->vr<=maxv){
dels.push_back(*F);
maxd=max(maxd,F->pre);
continue;
}
if(!newup)
nf=F->pre-(F->vr-F->vl);
break;
}
for(si F=--cht.lower_bound(node(first_up));F!=cht.begin();F--){
if(F->vr <=maxv){
dels.push_back(*F);
maxd=max(maxd,F->pre);
} else break;
}
for(int i=0;i<(int)dels.size();i++)
cht.erase(dels[i]);
cht.insert(node(maxd,1,maxv,newup));
if(nf!=-1) first_up=nf;
if(newup) first_up=maxd-maxv+1;
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
cht.insert(node(0,n+1,n+1,0));
cht.insert(node(n,1,n,1));
cht.insert(node(n+1,n+1,n+1,1));
for(int i=1;i <=m;i++){
int op,x;
cin>>op>>x;
if(op==0){
split(x+1);
if(!cht.lower_bound(node(x))->isup) continue;
else assign(getval(x),0);
} else{
split(x);
if(cht.lower_bound(node(x))->isup) continue;
else assign(getval(x),1);
}
}
for(si it=cht.begin();it!=cht.end();it++)
if(it->isup)
for(int i=it->vl;i<=it->vr;i++)
if(i!=n+1) cout<<i<<' ';else;
else
for(int i=it->vr;i>=it->vl;i--)
if(i!=n+1) cout<<i<<' ';else;
return 0;
}

浙公网安备 33010602011771号