【题目描述】
为了把工厂中高低不等的物品按从低到高排好序,工程师发明了一种排序机械臂。它遵循一个简单的排序规则,第一次操作找到高度最低的物品的位置 P 1 P_1 P1,并把左起第一个物品至 P 1 P_1 P1间的物品 (即区间 [ 1 , P 1 ] [1,P_1] [1,P1]间的物品) 反序;第二次找到第二低的物品的位置 P 2 P_2 P2,并把左起第二个至 P 2 P_2 P2间的物品 (即区间 [ 2 , P 2 ] [2,P_2] [2,P2]间的物品) 反序……最终所有的物品都会被排好序。
【输入格式】
第一行包含正整数n,表示需要排序的物品数量。
第二行包含n个空格分隔的整数 P i P_i Pi,表示每个物品的高度。
【输出格式】
输出一行包含n个空格分隔的整数
P
i
P_i
Pi。
【样例输入】
6
3 4 5 1 6 2
【样例输出】
4 6 4 5 6 6
【题意分析】
突然发现之前的博客都没有挂上
L
a
T
e
X
{L_a}{Te^X}
LaTeXqwq
题目大意就是:给你一些高度,每次都要暴力找到第 i i i低的位置 P i P_i Pi,然后将 i i i至 P i P_i Pi的高度全部翻转。
找第
k
k
k小?序列反转?这不就是
S
p
l
a
y
Splay
Splay裸题吗?
但是我们还要加一个小小的操作:给你的是一堆高度,总不可能把它们放进平衡树里面吧?那么我们用一个离散化的思想,把权值排序(注意要保持输入顺序,不然会WA),以排序后的新编号作为新权值放进
S
p
l
a
y
Splay
Splay里面,再插两个哨兵(便于提取区间)然后就可以愉快地
S
p
l
a
y
Splay
Splay了
如何找第
i
i
i小?我们把要找的那个点旋到根节点,左子树大小可以直接get,那么左子树节点个数+1就是
a
n
s
w
e
r
answer
answer,因为加了哨兵,再减去1即可。
如何进行区间翻转?用
K
t
h
Kth
Kth函数拿到位置之后,将l旋到根节点,将r旋到l(自己画图理解下)
与线段树类似, S p l a y Splay Splay中tag中存的是翻转标记,因此我们每次 s p l a y splay splay, K t h Kth Kth的时候要 p u s h d o w n pushdown pushdown翻转标记( p u s h d o w n pushdown pushdown过量好像不会影响结果?)。
p u s h u p pushup pushup过程其实就是更新子树大小。
Code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAX 200000
#define INF 1 << 29 //加哨兵用
using namespace std;
struct Splay{
int son[2];
int tag,size,father;
//Splay结构体,son[0]左儿子,son[1]右儿子,
//size[]子树大小,father[]父亲节点,tag[]翻转标记
}tree[MAX];
struct node{
int id,v;
}a[MAX]; //输入数据结构体
int root;
bool chk(node a,node b){
if (a.v<b.v)return 1;
else if(a.v==b.v)return a.id<b.id;
return 0;
} //判断函数:如果相等,输入顺序优先
inline void push_up(int x){
tree[x].size=tree[tree[x].son[1]].size+tree[tree[x].son[0]].size+1;
} //向上更新
inline void push_down(int x){
if (tree[x].tag){
if (tree[x].son[0])tree[tree[x].son[0]].tag^=1;
if (tree[x].son[1])tree[tree[x].son[1]].tag^=1;
swap(tree[x].son[0],tree[x].son[1]);
tree[x].tag=0;
}
} //向下更新
inline void rotate(int x){
int y=tree[x].father; //爸爸
int z=tree[y].father; //爷爷
int k=tree[y].son[1]==x; //x是爸爸的哪个儿子
int kk=tree[z].son[1]==y; //爸爸是爷爷的哪个儿子
tree[z].son[kk]=x; //x变成爷爷的儿子
tree[x].father=z; //x的爸爸变成爷爷
tree[y].son[k]=tree[x].son[k^1];
//y的原来是x的地方现在变成x的另一个儿子
tree[tree[x].son[k^1]].father=y;
//认新的爸爸
tree[x].son[k^1]=y; //被拿走的儿子变成了y
tree[y].father=x; //更新爸爸
push_up(y); //修改size[]
push_up(x); //修改size[]
} //Splay经典旋转操作
inline void build(int l,int r,int fa){
if (l>r)return;
int mid=(l+r) >> 1;
if (mid<fa)tree[fa].son[0]=mid;
else tree[fa].son[1]=mid;
tree[mid].size=1;tree[mid].father=fa;
if (l==r)return;
build(l,mid-1,mid);
build(mid+1,r,mid);
push_up(mid);
} //建树
inline void splay(int x,int goal){
while (tree[x].father!=goal){
int y=tree[x].father;
int z=tree[y].father;
push_down(z); //记得修改
push_down(y);
push_down(x);
if (z!=goal)
(tree[y].son[1]==z)^(tree[z].son[1]==y)
?rotate(x):rotate(y);
rotate(x);
}
if (!goal)root=x;
}
inline int Kth(int k){
int u=root;
while ("Forever"){
push_down(u);
if (tree[tree[u].son[0]].size>=k)u=tree[u].son[0];
else if (tree[tree[u].son[0]].size==k-1)return u;
else{
k-=tree[tree[u].son[0]].size+1;
u=tree[u].son[1];
}
}
} //查找Kth
inline void reverse(int l,int r){
l=Kth(l);
r=Kth(r+2);
splay(l,0);
splay(r,l);
tree[tree[tree[root].son[1]].son[0]].tag^=1;
} //翻转
int main(){
int n;
scanf("%d",&n);
for (register int i=2;i<=n+1;i++){
scanf("%d",&a[i].v);
a[i].id=i; //记录读入顺序
}
a[1].id=1;a[n+2].id=n+2;
a[1].v=-INF;a[n+2].v=INF; //插♂哨兵
sort(a+1,a+n+3,chk); //排序后才能Splay哦
build(1,n+2,0); //建树
root=(n+3) >> 1; //因为加了哨兵,所以root是这个
for (register int i=2;i<=n;i++){
splay(a[i].id,0);
int ans=tree[tree[root].son[0]].size+1; //左子树+1
printf("%d ",ans-1); //减去哨兵
reverse(i-1,ans-1); //翻转区间
}
printf("%d\n",n); //最后一个肯定是n
return 0;
}