题解: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;
}

posted @ 2026-01-30 10:11  concert_b  阅读(0)  评论(0)    收藏  举报