P4402 [Cerc2007]robotic sort 机械排序
541forever · · 题解
这是一道比较裸的平衡树模板题,这是一篇可能对细(keng)节(dian)解释比较详尽的屑题解。
思路与 文艺平衡树 相似,我们需要先增加两个哨兵节点,然后每次执行区间翻转操作时,就对区间左端点的前驱和右端点的后继进行 Splay 并把不断交换他们子节点的左右子树。而对于求解答案,我们要求的是这个节点的排名(即它在当前序列中的位置),我们可以将该节点提到根,然后统计它的左子树的大小 size(这个 size 没有算上这个节点本身,但算上了哨兵节点,所有两个影响抵消)。具体实现时,我们可以记录每个权值在平衡树中对应的节点编号记为 pos[i],然后每次翻转时找到当前节点 pos[i] 的后继,而在我们进行这一步操作前,将要操作的值前面的数已经是有序的了,所有它的前驱便是 pos[i-1]。
注意:
- 我们不太能直接以编号作为平衡树的权值进行操作,这样虽然仍能实现区间翻转,但查一个数在序列中的位置会较为麻烦。
- 区间翻转需要提到根上的点时左端点的前驱和右端点的后继,而不是左端点和右端点(本蒟蒻就是因为这里一直不知道哪错了)。
- 给定数组的值可能会有相等而我们需要按输入的原始次序操作,所以我们可以将原序列重新按离散的方式重新赋值。
下面是丑陋的代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,val[300005],tag[300005],ch[300005][2],tot,rt,fa[300005],si[300005],p[300005];//p记录权值在平衡树中对应的点的编号,tag作为懒标记记录是否需要交换左右子树
struct object{
int val,num;
}ob[300005];
inline int read(){
int x=0,f=1;
char ch;
do{
ch=getchar();
if(ch=='-'){
f=-1;
}
}while(!(ch>='0'&&ch<='9'));
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
struct Splay{
void push_up(int cur){
si[cur]=si[ch[cur][1]]+si[ch[cur][0]]+1;
}
int get(int x){
return x==ch[fa[x]][1];
}
int build(int l,int r,int f){
if(l>r){
return 0;
}
int mid=(l+r)>>1;
int now=++tot;
fa[now]=f;
val[now]=ob[mid].val;
p[ob[mid].val]=now;
ch[now][0]=build(l,mid-1,now);
ch[now][1]=build(mid+1,r,now);
push_up(now);
return now;
}
void push_down(int cur){
if(tag[cur]){
tag[cur]=0;
swap(ch[cur][0],ch[cur][1]);
tag[ch[cur][0]]^=1;
tag[ch[cur][1]]^=1;
}
}
int nex(){//找到后继
push_down(rt);
int x=ch[rt][1];
while(1){
push_down(x);
if(!ch[x][0]){
break;
}
x=ch[x][0];
}
return x;
}
void rotate(int x){
int y=fa[x];
int z=fa[y];
push_down(y);
push_down(x);
int chk=get(x);
ch[y][chk]=ch[x][chk^1];
if(ch[x][chk^1]){
fa[ch[x][chk^1]]=y;
}
fa[y]=x;
ch[x][chk^1]=y;
fa[x]=z;
if(z){
ch[z][y==ch[z][1]]=x;
}
push_up(y);
push_up(x);
}
void splay(int x,int y){
for(int f;(f=fa[x])!=y;rotate(x)){
if(fa[f]==y){
continue;
}
if(get(x)==get(f)){
rotate(f);
}else{
rotate(x);
}
}
if(y==0){
rt=x;
}
}
void reverse(int l,int r){
splay(l,0);
splay(r,l);
tag[ch[r][0]]^=1;
}
}tree;//Splay模板
bool cmp(object a,object b){
if(a.val<b.val){
return 1;
}else{
if(a.val==b.val){
return a.num<b.num;
}
}
return 0;
}
bool cmp2(object a,object b){
return a.num<b.num;
}
signed main(){
n=read();
for(int i=1;i<=n;++i){
ob[i+1].val=read();
ob[i+1].num=i+1;
}
ob[1].val=0;
ob[n+2].val=n+1;
sort(ob+2,ob+n+2,cmp);
for(int i=2;i<=n+1;i++){
//离散
ob[i].val=i-1;
}
sort(ob+2,ob+2+n,cmp2);
rt=tree.build(1,n+2,0);//以类似线段树的方式建数
for(int i=1;i<=n;++i){
int x=p[i];//找到该权值对应的点的位置
tree.splay(x,0);
printf("%lld ",si[ch[x][0]]);
x=tree.nex();//该权值的后继
int y=p[i-1];//该权值的前驱
tree.reverse(y,x);//区间翻转
}
return 0;
}