HDU 1890(splay tree 区间翻转)

前后折腾了一天半时间才搞定。。从学习lazy到理解代码。。—_—||

题意是说每次把第i大的数所在位置和第i个位置之间翻转,输出每个数在翻转前的位置。

首先我们要想到,在splay tree 中,对于根节点来说,左子树的大小+1就是它在数组中的位置(从1开始标号),左子树的各元素也是在数列中位于根结点左边的

我们现在用splay tree来维护未排序的序列,那对于题目要求的翻转操作来说,对象就仅仅是根节点的左子树了

我们要做的事情可以抽象成这样:每次把第i大的结点旋转至根,再把根节点的左子树打上rev标记,输出答案i+s[ch[root][0]],然后删除根节点

对于答案的理解,可以分为两部分,首先i是至在数列左边已排序的序列(包括待排序的根节点),s[ch[root][0]]则是在目前操作的结点左边的节点数

对于lazy标记的理解建议最好画画图

现在要解决的问题有两个:1、如何找到第i大的结点 2.确定该结点在splay tree中的位置

对于第一个问题,我们把数列先排序(记得把序列的id同时捆绑),那我们只需要依次对这n个数依次处理就好

第二个问题要稍微难理解一些。这里也稍稍用了离散化的思想。我们把数列按照要求排序后,按照id(原序列次序)相当于离散成1~n这n个数。

我们建立一棵结点编号为1~n的平衡树作为初始状态

则不难理解这棵树维护的是排序前每个数的id,也就是说维护的是原序列

其次要先理解splay tree中结点编号和该结点在原数列的下标的关系——相等。

举个例子:现在要操作第3个结点,他的id是5,也就是说这是第3大的结点,在原序列中在第5个位置。

     那我把5号结点旋转到根,看看左子树的大小(相当于看看原序列中5号位置左边还剩几个元素,因为第1第2大的都删了)

代码是参考网上的。

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"vector"
#define ll long long
#define mems(a,b) memset(a,b,sizeof(a))

using namespace std;
const int MAXN = 1e5+50;
const int MAXE = 200500;
const int INF = 0x3f3f3f;

int pre[MAXN],ch[MAXN][2],rev[MAXN],s[MAXN];
int root,n;

struct Node{
    int w,id;
}node[MAXN];

bool cmp(Node a,Node b){
    if(a.w==b.w) return a.id<b.id;
    return a.w<b.w;
}

void newnode(int &pos,int fa,int k){
    pos=k;
    pre[pos]=fa;
    ch[pos][0]=ch[pos][1]=0;
    rev[pos]=0;
    s[pos]=1;
}

void pushdown(int x){
    if(rev[x]){
        rev[ch[x][0]]^=1;
        rev[ch[x][1]]^=1;
        swap(ch[x][0],ch[x][1]);
        rev[x]=0;
    }
}

void pushup(int x){
    s[x]=s[ch[x][0]]+s[ch[x][1]]+1;
}

void build(int &x,int l,int r,int fa){///建立平衡树
    if(l>r) return;
    int mid=(l+r)>>1;
    newnode(x,fa,mid);
    build(ch[x][0],l,mid-1,x);
    build(ch[x][1],mid+1,r,x);
    pushup(x);
}

void init(){
    root=0;
    sort(node+1,node+1+n,cmp);
    build(root,1,n,0);
}

void Rotate(int x,int kind){
    int fa=pre[x];
    pushdown(fa);
    pushdown(x);        ///注意先后次序
    ch[fa][!kind]=ch[x][kind];
    pre[ch[x][kind]]=fa;

    if(pre[fa]) ch[pre[fa]][ch[pre[fa]][1]==fa]=x;
    pre[x]=pre[fa];

    ch[x][kind]=fa;
    pre[fa]=x;
    pushup(fa);         ///注意先后次序
    pushup(x);
}

void Splay(int r,int goal){
    pushdown(r);
    while(pre[r]!=goal){
        if(pre[pre[r]]==goal){
            pushdown(pre[r]);       ///不可省去,会影响ch[pre[r]][0],下面类似语句同理不可省
            pushdown(r);
            Rotate(r,ch[pre[r]][0]==r);
        }
        else{
            int fa=pre[r];
            pushdown(pre[fa]);      ///先pushdown再求kind -_-|| 因为这个TLE了半天
            pushdown(pre[r]);
            pushdown(r);
            int kind=(ch[pre[fa]][0]==fa);
            if(ch[fa][kind]==r){
                Rotate(r,!kind);
                Rotate(r,kind);
            }
            else{
                Rotate(fa,kind);
                Rotate(r,kind);
            }
        }
    }
    pushup(r);
    if(!goal) root=r;
}

int getmax(int x){      ///寻找比x小但是最接近x的数
    pushdown(x);
    while(ch[x][1]){
        x=ch[x][1];
        pushdown(x);
    }
    return x;
}

void del(int x){
    if(!ch[root][0]){
        root=ch[root][1];
        pre[root]=0;
    }
    else{
        int t=getmax(ch[root][0]);
        Splay(t,root);
        ch[t][1]=ch[root][1];
        pre[ch[root][1]]=t;
        root=t;
        pre[root]=0;
        pushup(root);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(scanf("%d",&n)&&n){
        for(int i=1;i<=n;i++){
            scanf("%d",&node[i].w);
            node[i].id=i;
        }
        init();
        for(int i=1;i<n;i++){
            Splay(node[i].id,0);
            rev[ch[root][0]]^=1;
            printf("%d ",i+s[ch[root][0]]);
            del(root);
        }
        printf("%d
",n);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/luxiaoming/p/5129862.html