[CQOI2014]排序机械臂

Posted 日拱一卒 功不唐捐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CQOI2014]排序机械臂相关的知识,希望对你有一定的参考价值。

洛谷P3165 [CQOI2014]排序机械臂

https://www.luogu.org/problem/show?pid=3165

题目描述

为了把工厂中高低不等的物品按从低到高排好序,工程师发明了一种排序机械臂。它遵循一个简单的排序规则,第一次操作找到摄低的物品的位置P1,并把左起第一个至P1间的物品反序;第二次找到第二低的物品的位置P2,并把左起第二个至P2间的物品反序...最终所有的物品都会被排好序。

上图给出_个示例,第_次操作前,菝低的物品在位置4,于是把第1至4的物品反序;第二次操作前,第二低的物品在位罝6,于是把第2至6的物品反序...

你的任务便是编写一个程序,确定一个操作序列,即每次操作前第i低的物品所在位置Pi,以便机械臂工作。需要注意的是,如果有高度相同的物品,必须保证排序后它们的相对位置关系与初始时相同。

输入输出格式

输入格式:

 

第一行包含正整数n,表示需要排序的物品数星。

第二行包含n个空格分隔的整数ai,表示每个物品的高度。

 

输出格式:

 

输出一行包含n个空格分隔的整数Pi。

 

输入输出样例

输入样例#1:
6
3 4 5 1 6 2
输出样例#1:
4 6 4 5 6 6
基本框架:splay 区间反转+查询
流程:
1、数据排序
排序规则:高度小的在前,若高度相同,位置靠前的在前
2、建立平衡树,平衡树中直接存储物品的编号,即加上两个虚拟节点后,平衡树中的节点x,对应初始序列位置为x的点;平衡树的中序遍历,对应本次排序后的物品顺序
物品的高度只在排序中有用,自建树开始高度完全没有用了
3、对于输出物品位置p,将物品旋转至根节点,输出左子树大小+1即可
4、区间反转
注意维护反转标记
#include<cstdio>
#include<algorithm>
#define N 100005
using namespace std;
int n,fa[N],siz[N],ch[N][2],root,tag[N];
struct node
{
    int i,k;
}a[N];
inline bool cmp(node p,node q)
{
    if(p.k<q.k) return 1;
    if(p.k==q.k) return p.i<q.i;
    return 0;
}
inline void update(int k)
{
    siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+1;
} 
inline void build(int l,int r,int f)
{
    if(l>r) return;
    int mid=l+r>>1;
    if(mid<f) ch[f][0]=mid;
    else ch[f][1]=mid;
    siz[mid]=1;fa[mid]=f;
    if(l==r) return;
    build(l,mid-1,mid);
    build(mid+1,r,mid);
    update(mid);
}
inline void down(int k)
{
    tag[k]^=1;
    if(ch[k][0]) tag[ch[k][0]]^=1;
    if(ch[k][1]) tag[ch[k][1]]^=1;
    swap(ch[k][0],ch[k][1]);
}
inline void rotate(int x,int & goal)
{
    int y=fa[x],z=fa[y],kind=ch[y][1]==x;
    if(y==goal) goal=x;
    else ch[z][ch[z][1]==y]=x;
    ch[y][kind]=ch[x][kind^1];ch[x][kind^1]=y;
    fa[y]=x;fa[x]=z;fa[ch[y][kind]]=y;
    update(y);
}
inline void splay(int x,int & goal)
{
    while(x!=goal)
    {
        int y=fa[x],z=fa[y];
        if(tag[z]) down(z);
            if(tag[y]) down(y);
                if(tag[x]) down(x);
        if(y!=goal)
        {
            if(ch[y][1]==x^ch[z][1]==y) rotate(x,goal);
            else rotate(y,goal);
        }
        rotate(x,goal);    
        update(x);
    }
} 
inline int find(int now,int k)
{
    if(tag[now]) down(now);
    int l=ch[now][0],r=ch[now][1];
    if(siz[l]+1==k) return now;
    if(k<=siz[l])  return find(l,k);
    return find(r,k-siz[l]-1);
}
inline void reverse(int l,int r)
{
    int x=find(root,l-1),y=find(root,r+1);
    splay(x,root);splay(y,ch[x][1]);
    tag[ch[y][0]]^=1;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {scanf("%d",&a[i+1].k); a[i+1].i=i+1;}
    a[1].i=1;a[n+2].i=n+2;a[n+2].k=2000000001;
    sort(a+1,a+n+3,cmp);
    build(1,n+2,0);root=n+3>>1;
    int p;
    for(int i=2;i<=n;i++)
    {
        splay(a[i].i,root);
        p=siz[ch[root][0]]+1;
        printf("%d ",p-1);
        reverse(i,p);
    }
    printf("%d",n);
}

3个错误:

①、标记下传

a.错误代码:

inline void down(int k)
{
tag[k]^=1;
tag[ch[k][0]]^=1;tag[ch[k][1]]^=1;
swap(ch[k][0],ch[k][1]);
}

错因:没有判断是否有左右孩子,如果没有孩子,为0,标记下穿至0号节点,在区间范围锁定时,如果l=1,查找0号节点,因为建树的时候,最初的父节点为0,所以就会误将0号节点的tag下传

b.splay中没有标记下传

 

if(tag[z]) down(z);
if(tag[y]) down(y);
if(tag[x]) down(x);

以前写的那个splay模板因为splay在find之后,而find标记下传了,所以splay不需要下传

 

②、排序

错误代码:

inline bool cmp(node p,node q)
{
return p.k<=q.k;
}

不能完成k相同时位置靠前的在前

③、查找位置

开始写了一个splay模板中的查找代码

错误代码:

inline int findpos(int now,int x)
{
if(x==now) return 1;
if(tag[now]) down(now);
if(x<now) return findpos(ch[now][0],x);
return siz[ch[now][0]]+1+findpos(ch[now][1],x);
}

错因:虽然一开始建立平衡树是按编号的大小顺序建立的,但建立后,区间的反转时平衡树的节点间没有大小关系,所以不能根据now和x的大小查找x

其实只要将x旋转至根节点,输出左子树大小+1即可

思维太死板、模式化

 

以上是关于[CQOI2014]排序机械臂的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 3506[Cqoi2014]排序机械臂 - splay

P3165 [CQOI2014]排序机械臂

Bzoj3506: [Cqoi2014]排序机械臂

P3165 [CQOI2014]排序机械臂

LibreOJ2241 - 「CQOI2014」排序机械臂

Luogu P3165 [CQOI2014]排序机械臂