POJ 3580

题意:给一个数组,有6种操作:

add  x y d [x,y]中所有数加d

reverse x y: [x,y]区间翻转

revolve x y t: [x,y]循环右移t位,t可取任意整数

insert  x v: 在第x个位置插入一个值为v的数

delete x : 删除第x个数

min x y : 求区间[x,y]的最小值

题解:典型splay,通过旋转使得要处理的区间转到root的右孩子的左子树上,然后所有操作均在这上面处理:

add/reverse :可以直接用标记处理,在下次旋转时通过下传标记解决

revolve :则可看做把最右边含t个结点的子树嫁接到最左端去

insert :可以将要插入位置转到根节点,然后把它下一个结点转到根节点的右孩子上,这样直接在这两个结点间添加一个结点便可以解决

delete :将x位置上一位转到根节点,x位置下一位转到根节点的右孩子上,然后删除其左孩子。

这是我第一棵splay,看论文花了半天,写花了半天,调试用了一天,re无数,wa一次,反反复复优化,最后勉强进入一秒。若光从旋转操作上看splay,它的确是个水结构,但是各种翻转,轮换叠加在一起也足以让人蛋疼一阵子了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef struct sp_node * Node;
const int MAXN=210010,inf=100000000;
struct sp_node
{
    Node pre,ch[2];
    int value,lazy,Min,size;//结点价值,lazy标记,子树最小值,子树大小
    bool rev;//是否旋转
    void init(int _value)
    {
        pre=ch[0]=ch[1]=NULL;
        Min=value=_value;
        lazy=0;
        size=1;
        rev=false;
    }
} nstack[MAXN];
int scnt;//静态申明splay结点的栈顶位置
Node root;
inline int getsize(Node &x)//取得x子树大小,主要是解决x=NULL的情况
{
    return x?x->size:0;
}
void pushdown(Node &x)//将x标记下移
{
    if(!x)
        return;
    if(x->lazy)
    {
        int w=x->lazy;
        x->value+=w;
        if(x->ch[0])
        {
            x->ch[0]->lazy+=w;
            x->ch[0]->Min+=w;
        }
        if(x->ch[1])
        {
            x->ch[1]->lazy+=w;
            x->ch[1]->Min+=w;
        }
        x->lazy=0;
    }
    if(x->rev)
    {
        Node t=x->ch[0];
        x->ch[0]=x->ch[1];
        x->ch[1]=t;
        x->rev=false;
        if(x->ch[0])
            x->ch[0]->rev^=1;
        if(x->ch[1])
            x->ch[1]->rev^=1;
    }
}
void updata(Node &x)//更新x结点信息
{
    if(!x)
        return;
    x->size=1;
    x->Min=x->value;
    if(x->ch[0])
    {
        x->Min=min(x->Min,x->ch[0]->Min);
        x->size+=x->ch[0]->size;
    }
    if(x->ch[1])
    {
        x->Min=min(x->Min,x->ch[1]->Min);
        x->size+=x->ch[1]->size;
    }
}
void rotate(Node &x, int d) // 旋转操作,d=0 表示左旋,d=1 表示右旋
{
    Node y=x->pre;
    pushdown(y);
    pushdown(x);
    pushdown(x->ch[d]);
    y->ch[!d]=x->ch[d];
    if (x->ch[d]!=NULL) x->ch[d]->pre=y;
    x->pre = y->pre;
    if (y->pre!=NULL)
    {
        if (y->pre->ch[0]==y) y->pre->ch[0]=x;
        else y->pre->ch[1]=x;
    }
    x->ch[d]=y,y->pre=x;
    updata(y);
    if (y == root)//因为是指针,所以root可能被转下去了
        root = x;
}
void splay(Node &x, Node &f) // Splay 操作,表示把结点x 转到结点f
{
    for (pushdown(x); x!=f;)
    {
        if(x->pre==f)
        {
            if(f->ch[0]==x)
                rotate(x,1);
            else
                rotate(x,0);
            break;
        }
        else
        {
            Node y=x->pre,z=y->pre;
            if (z->ch[0] == y)
                if (y->ch[0] == x)
                    rotate(y,1),rotate(x, 1); // 一字形旋转
                else
                    rotate(x, 0), rotate(x, 1); // 之字形旋转
            else if (y->ch[1] == x)
                rotate(y, 0), rotate(x, 0); // 一字形旋转
            else
                rotate(x, 1), rotate(x, 0); // 之字形旋转
            if(z==f)//转了之后,x就到了原来z的位置,如果z就是要到的地方,就可以退出了
                break;
        }
        updata(x);
    }
    updata(x);
}
void select(int k, Node &f)
{
    int tmp;
    Node t;
    for(t=root;;)
    {
        pushdown(t);
        tmp=getsize(t->ch[0]); // 得到t 左子树的大小
        if (k == tmp + 1) break; // 得出t 即为查找结点,退出循环
        if (k <= tmp) // 第k 个结点在t 左边,向左走
            t = t->ch[0];
        else // 否则在右边,而且在右子树中,这个结点不再是第k 个
            k -=tmp+1,t=t->ch[1];
    }
    pushdown(t);
    splay(t, f); // 执行旋转
}
void insert(int pos,int value)
{
    select(pos+1,root);
    select(pos+2,root->ch[1]);
    Node t=nstack+scnt++,x=root->ch[1];
    pushdown(root);
    pushdown(x);
    t->init(value);
    t->ch[1]=x;
    x->pre=t;
    root->ch[1]=t;
    t->pre=root;
    splay(x,root);
}
void add(int a,int b,int d)
{
    select(a,root);
    select(b+2,root->ch[1]);
    Node x=root->ch[1]->ch[0];
    pushdown(x);
    updata(x);
    x->Min+=d;
    x->lazy+=d;
    splay(x,root);
}
void reverse(int a,int b)
{
    select(a,root);
    select(b+2,root->ch[1]);
    root->ch[1]->ch[0]->rev^=1;
    Node x=root->ch[1]->ch[0];
    splay(x,root);
}
void revolve(int a,int b,int t)
{
    Node p1,p2;
    select(a,root);
    select(b+2,root->ch[1]);
    select(b+1-t,root->ch[1]->ch[0]);
    p1=root->ch[1]->ch[0];
    pushdown(p1);
    p2=p1->ch[1];
    p1->ch[1]=NULL;
    select(a+1,root->ch[1]->ch[0]);
    p1=root->ch[1]->ch[0];
    pushdown(p1);
    p1->ch[0]=p2;
    p2->pre=p1;
    splay(p2,root);
}
int getmin(int a,int b)
{
    select(a,root);
    select(b+2,root->ch[1]);
    Node x=root->ch[1];
    pushdown(x);
    x=x->ch[0];
    pushdown(x);
    updata(x);
    return x->Min;
}
void erase(int pos)
{
    select(pos,root);
    select(pos+2,root->ch[1]);
    pushdown(root->ch[1]);
    root->ch[1]->ch[0]=NULL;
    Node x=root->ch[1];
    splay(x,root);
}
int main()
{
    int i,a,b,c,k,n,m;
    scnt=0;
    root=nstack+scnt++;
    root->init(inf);
    root->ch[1]=nstack+scnt++;
    root->ch[1]->init(inf);
    scanf("%d",&n);
    for(i=0; i<n; i++)
    {
        scanf("%d",&c);
        insert(i,c);
    }
    scanf("%d",&m);
    while(m--)
    {
        char s[50];
        scanf("%s", s);
        if(s[0]=='A')
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
        }
        else if(s[0]=='R')
        {
            scanf("%d%d",&a,&b);
            if(s[3]=='E')
                reverse(a,b);
            else
            {
                scanf("%d",&k);
                int tn=b-a+1;
                k=(k%tn+tn)%tn;
                if(a==b||k==0)
                    continue;
                revolve(a,b,k);
            }
        }
        else if(s[0]=='I')
        {
            scanf("%d%d",&a,&c);
            insert(a,c);
        }
        else if(s[0]=='D')
        {
            scanf("%d",&a);
            erase(a);
        }
        else
        {
            scanf("%d%d",&a,&b);
            int ans=getmin(a,b);
            printf("%d\n",ans);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值