[CQOI2014]排序机械臂——[splay]

【题目描述】

为了把工厂中高低不等的物品按从低到高排好序,工程师发明了一种排序机械臂。它遵循一个简单的排序规则,第一次操作找到高度最低的物品的位置 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;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值