洛谷 P2042 [NOI2005] 维护数列(splay)

题目描述

请写一个程序,要求维护一个数列,支持以下 6 种操作:

编号名称格式说明
1插入INSERT⁡ 𝑝𝑜𝑠𝑖 𝑡𝑜𝑡 𝑐1 𝑐2⋯𝑐𝑡𝑜𝑡在当前数列的第 𝑝𝑜𝑠𝑖 个数字后插入 𝑡𝑜𝑡个数字:𝑐1,𝑐2⋯𝑐𝑡𝑜𝑡;若在数列首插入,则 𝑝𝑜𝑠𝑖 为 0
2删除DELETE⁡ 𝑝𝑜𝑠𝑖 𝑡𝑜𝑡从当前数列的第 𝑝𝑜𝑠𝑖 个数字开始连续删除 𝑡𝑜𝑡 个数字
3修改MAKE-SAME⁡ 𝑝𝑜𝑠𝑖 𝑡𝑜𝑡 𝑐从当前数列的第 𝑝𝑜𝑠𝑖个数字开始的连续 𝑡𝑜𝑡个数字统一修改为 𝑐
4翻转REVERSE⁡ 𝑝𝑜𝑠𝑖 𝑡𝑜𝑡取出从当前数列的第 𝑝𝑜𝑠𝑖 个数字开始的 𝑡𝑜𝑡个数字,翻转后放入原来的位置
5求和GET-SUM⁡ 𝑝𝑜𝑠𝑖 𝑡𝑜𝑡计算从当前数列的第 𝑝𝑜𝑠𝑖个数字开始的 𝑡𝑜𝑡个数字的和并输出
6求最大子列和MAX-SUM⁡求出当前数列中和最大的一段子列,并输出最大和

输入格式

第一行包含两个整数 𝑁 和 𝑀,𝑁 表示初始时数列中数的个数,𝑀 表示要进行的操作数目。

第二行包含 𝑁 个数字,描述初始时的数列。以下 𝑀行,每行一条命令,格式参见问题描述中的表格。

输出格式

对于输入数据中的 GET-SUM⁡ 和 MAX-SUM⁡操作,向输出文件依次打印结果,每个答案(数字)占一行。

输入输出样例

输入 #1复制

9 8 
2 -6 3 5 1 -5 -3 6 3 
GET-SUM 5 4
MAX-SUM
INSERT 8 3 -5 7 2
DELETE 12 1
MAKE-SAME 3 3 2
REVERSE 3 6
GET-SUM 5 4
MAX-SUM

输出 #1复制

-1
10
1
10

说明/提示

数据规模与约定
  • 你可以认为在任何时刻,数列中至少有 1 个数。
  • 输入数据一定是正确的,即指定位置的数在数列中一定存在。
  • 对于 50% 的数据,任何时刻数列中最多含有 3×1e4个数。
  • 对于 100%的数据,任何时刻数列中最多含有 5×1e5个数,任何时刻数列中任何一个数字均在 [−103,103]内,1≤𝑀≤2×1e4,插入的数字总数不超过 4×1e6。

思路:

对于本题,我们需要维护以下几个信息:

        (1)区间是否需要反转的懒标记rev

        (2)区间是否替换成同一个数的懒标记same

        (3)区间大小size

        (4)区间总和sum

        (5)区间最大子序列ms

        (6)区间最大前缀子序列ls

        (7)区间最大后缀子序列rs

如何维护信息呢?

        (1)插入,当需要再数列第pos个数字后面插入一段序列时,我们需要splay(pos,0),splay(pos+1,pos),将pos旋转到根,将pos+1旋转到pos的下面,此时,我们只需要构建出插入的序列,然后将其插入到pos+1节点的左子树上即可。

        (2)删除,当需要删除pos开始的tot个数字时,我们只需要splay(pos-1,0),splay(pos+tot,pos-1),然后将pos+tot的左子树删除即可。

        (3)如果需要修改一个区间的话,我们就直接将它的懒标记更新

        (4)如果需要反转的话,也更新懒标记

        (5)求和和求最大子序列和线段树维护的方法一样。

由于本题数据量过大,需要回收节点。

AC代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=500010,INF=1e9;

int n,m;
struct Node
{
    int s[2],p,v;
    int rev;//是否翻转
    int same;//是否替换成同一个数
    int size;//大小
    int sum;//区间和
    int ms;//区间最大子序列
    int ls;//前缀最大序列
    int rs;//后缀最大序列

    void init(int _v,int _p)
    {
        s[0]=s[1]=0;
        p=_p;
        v=_v;
        rev=same=0;
        size=1;
        sum=ms=v;
        ls=rs=max(v,0);
    }
}tr[N];

int root,nodes[N],tt;//回收站
int w[N];//序列

void pushup(int x)
{
    auto &u=tr[x],&l=tr[u.s[0]],&r=tr[u.s[1]];
    u.size=l.size+r.size+1;
    u.sum=l.sum+r.sum+u.v;
    u.ls=max(l.ls,l.sum+u.v+r.ls);
    u.rs=max(r.rs,r.sum+u.v+l.rs);
    u.ms=max(max(l.ms,r.ms),l.rs+u.v+r.ls);
}

void pushdown(int x)
{
    auto &u=tr[x],&l=tr[u.s[0]],&r=tr[u.s[1]];
    if(u.same)//如果这个区间变成同一个数,那翻转不翻转都一样
    {
        u.same=u.rev=0;
        if(u.s[0])
            l.same=1,l.v=u.v,l.sum=l.v*l.size;
        if(u.s[1])
            r.same=1,r.v=u.v,r.sum=r.v*r.size;
        if(u.v>0)
        {
            if(u.s[0])
                l.ms=l.ls=l.rs=l.sum;
            if(u.s[1])
                r.ms=r.ls=r.rs=r.sum;
        }
        else
        {
            if(u.s[0])
                l.ms=l.v,l.ls=l.rs=0;
            if(u.s[1])
                r.ms=r.v,r.ls=r.rs=0;
        }
    }
    else if(u.rev)
    {
        u.rev=0,l.rev^=1,r.rev^=1;
        swap(l.ls,l.rs),swap(r.ls,r.rs);
        swap(l.s[0],l.s[1]),swap(r.s[0],r.s[1]);
    }
}

void rotate(int x)//左旋右旋
{
    int y=tr[x].p;
    int z=tr[y].p;
    int k=tr[y].s[1]==x;
    tr[z].s[tr[z].s[1]==y]=x,tr[x].p=z;
    tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].p=y;
    tr[x].s[k^1]=y,tr[y].p=x;
    pushup(y);
    pushup(x);
}

void splay(int x,int k)
{
    while(tr[x].p!=k)
    {
        int y=tr[x].p;
        int z=tr[y].p;
        if(z!=k)
        {
            if((tr[y].s[1]==x)^(tr[z].s[1]==y))
                rotate(x);
            else 
                rotate(y);
        }
        rotate(x);
    }
    if(!k)
        root=x;
}

int get_k(int k)
{
    int u=root;
    while(u)
    {
        pushdown(u);
        if(tr[tr[u].s[0]].size>=k)
            u=tr[u].s[0];
        else if(tr[tr[u].s[0]].size+1==k)
            return u;
        else
        {
            k-=tr[tr[u].s[0]].size+1;
            u=tr[u].s[1];
        }
    }
}

int build(int l,int r,int p)
{
    int mid=l+r>>1;
    int u=nodes[tt--];//根节点
    tr[u].init(w[mid],p);
    if(l<mid)
        tr[u].s[0]=build(l,mid-1,u);
    if(mid<r)
        tr[u].s[1]=build(mid+1,r,u);
    pushup(u);
    return u;
}

void dfs(int u)//内存回收
{
    if(tr[u].s[0])
        dfs(tr[u].s[0]);
    if(tr[u].s[1])
        dfs(tr[u].s[1]);
    nodes[++tt]=u;
}

int main()
{
    for(int i=1;i<N;i++) nodes[++tt]=i;
    scanf("%d%d",&n,&m);
    tr[0].ms=-INF;
    w[0]=w[n+1]=-INF;//哨兵节点
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    
    root=build(0,n+1,0);
    
    char op[20];
    while(m--)
    {
        scanf("%s",op);
        if(!strcmp(op,"INSERT"))
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            for(int i=0;i<tot;i++) scanf("%d",&w[i]);
            int l=get_k(posi+1);//由于有哨兵节点,所以是posi+1
            int r=get_k(posi+2);
            splay(l,0);
            splay(r,l);
            int u=build(0,tot-1,r);
            tr[r].s[0]=u;
            pushup(r);
            pushup(l);
        }
        else if(!strcmp(op,"DELETE"))
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            int l=get_k(posi);
            int r=get_k(posi+tot+1);
            splay(l,0);
            splay(r,l);
            dfs(tr[r].s[0]);//将r的左子树删掉
            tr[r].s[0]=0;
            pushup(r);
            pushup(l);
        }
        else if(!strcmp(op,"MAKE-SAME"))
        {
            int posi,tot,c;
            scanf("%d%d%d",&posi,&tot,&c);
            int l=get_k(posi);
            int r=get_k(posi+tot+1);
            splay(l,0);
            splay(r,l);
            auto &son=tr[tr[r].s[0]];
            son.same=1;
            son.v=c;
            son.sum=c*son.size;
            
            if(c>0)
                son.ms=son.ls=son.rs=son.sum;
            else
                son.ms=c,son.ls=son.rs=0;
                
            pushup(r);
            pushup(l);
        }
        else if(!strcmp(op,"REVERSE"))
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            int l=get_k(posi);
            int r=get_k(posi+tot+1);
            splay(l,0);
            splay(r,l);
            auto &son=tr[tr[r].s[0]];
            son.rev^=1;
            swap(son.ls,son.rs);//反转的时候区间最大前缀变成后缀,最大后缀变成前缀
            swap(son.s[0],son.s[1]);//交换左右结点
            pushup(r);
            pushup(l);
        }
        else if(!strcmp(op,"GET-SUM"))
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            int l=get_k(posi);
            int r=get_k(posi+tot+1);
            splay(l,0);
            splay(r,l);
            printf("%d\n",tr[tr[r].s[0]].sum);
        }
        else 
            printf("%d\n",tr[root].ms);
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值