JZOJ2413. 【NOI2005】维护数列

本文详细介绍了一种高效的数据结构——Splay树,在解决包含插入、删除、修改、翻转及求和等多种操作的问题时的应用。文章提供了完整的代码实现,并解释了如何通过懒标记来优化操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

维护一个数列,支持以下操作:

  • 插入 INSERT_posi_tot_c1_c2_…_ctot 在当前数列的第posi个数字后插入tot个数字:c1, c2, …, ctot;若在数列首插入,则posi为0
  • 删除 DELETE_posi_tot 从当前数列的第posi个数字开始连续删除tot个数字
  • 修改 MAKE-SAME_posi_tot_c 将当前数列的第posi个数字开始的连续tot个数字统一修改为c
  • 翻转 REVERSE_posi_tot 取出从当前数列的第posi个数字开始的tot个数字,翻转后放入原来的位置
  • 求和 GET-SUM_posi_tot 计算从当前数列开始的第posi个数字开始的tot个数字的和并输出
  • 求和最大的子列 MAX-SUM 求出当前数列中和最大的一段子列,并输出最大和

Data Constraint
你可以认为在任何时刻,数列中至少有1个数。
  输入数据一定是正确的,即指定位置的数在数列中一定存在。
  50%的数据中,任何时刻数列中最多含有30 000个数;
  100%的数据中,任何时刻数列中最多含有500 000个数。
  100%的数据中,任何时刻数列中任何一个数字均在[-1 000, 1 000]内。
  100%的数据中,M ≤20 000,插入的数字总数不超过4 000 000个,输入文件大小不超过20MBytes。

题解

这么直白的题面,显然就是数据结构。又看到有翻转操作,那基本就可以确定是Splay了。
具体而言,Splay维护以下信息(以u为根节点的子树):

  • 子树大小Size[u]
    • 赋值标记SAME[u]
    • 翻转标记REV[u]
    • 子树所有值之和Sum[u]
    • 子树最大子序列之和MAXSUM[u]
    • 区间最大前缀和Lsum[u]
    • 区间最大后缀和Rsum[u]
      易得
      MAXSUM[u]=Max(MAXSUM[L],MAXSUM[R],Rsum[L]+Val[u]+Lsum[R])
      再维护以下操作

    GetRange( l , r )

    l旋到根,r旋到l的右儿子。此时r的右儿子就是区间[l+1,r1]

    void GetRange( int l , int r ) {
        int x = Get(l) ;
        int y = Get(r) ;
        Splay( x , 0 ) ;
        Splay( y , Root ) ;
    }

    Insert( pos , num )

    在第pos个数后插入num个数。
    先将pos旋到根,再将pos+1旋到右儿子,即GetRange( pos + 1 , pos + 2 )(+1是因为之前在首尾各加了一个节点,下同)

    void Insert( int pos , int num ) {
        GetRange( pos + 1 , pos + 2 ) ;
        Son[Son[Root][1]][0] = Build( 1 , num , Son[Root][1] ) ;
        Update( Son[Root][1] ) ;
        Update( Root ) ;
    }

    Delete( l , r )

    删除区间[l,r]
    还是先旋根取区间,再直接删除即可。
    这题会卡空间,所以删除的时候要将删除的节点编号放入一个队列里以备下一次插入节点时使用,即GetID

    void Delete( int l , int r ) {
        GetRange( l , r + 2 ) ;
        int R = Son[Root][1] ;
        GetID( Son[R][0] ) ;
        Pre[Son[R][0]] = 0 ;
        Son[R][0] = 0 ;
        Update( R ) ;
        Update( Root ) ;
    }

    MAKE_SAME( l , r , c )

    将区间[l,r]全部修改为c
    直接在SAME上打标记。

    void MAKE_SAME( int l , int r , int c ) {
        GetRange( l , r + 2 ) ;
        int L = Son[Son[Root][1]][0] ;
        SAME[L] = c ;
        Push( Son[Root][1] ) ;
        Update( Son[Root][1] ) ;
        Update( Root ) ;
    }

    Reverse( l , r )

    翻转区间[l,r]
    直接打标记。

    void Reverse( int l , int r ) {
        GetRange( l , r + 2 ) ;
        int L = Son[Son[Root][1]][0] ;
        REV[L] ^= 1 ;
        Push( Son[Root][1] ) ;
        Update( Son[Root][1] ) ;
        Update( Root ) ;
    }

    GET_SUM( l , r )

    求区间[l,r]的和。
    直接返回信息。

    int GET_SUM( int l , int r ) {
        GetRange( l , r + 2 ) ;
        int L = Son[Son[Root][1]][0] ;
        Push( L ) ;
        return Sum[L] ;
    }

    从这题也可以看出Splay在诸如区间修改,区间翻转等操作上的优势。且整体代码还是比较直观易懂的。
    需要注意的是,由于大量运用懒标记,一定要及时更新标记!!!

    SRC

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std ;
    
    #define N 2000000 + 10
    
    bool REV[N] ;
    int MAXSUM[N] , Lsum[N] , Rsum[N] , SAME[N] ;
    int Pre[N] , Son[N][2] , Sum[N] , Size[N] , Val[N] ;
    int A[N] , D[N] , S[N] ;
    char op[20] ;
    int n , m , Root , tot , top ;
    
    void Push( int v ) {
        if ( !v ) return ;
        int L = Son[v][0] ;
        int R = Son[v][1] ;
        if ( REV[v] ) {
            REV[v] ^= 1 ;
            REV[L] ^= 1 ;
            REV[R] ^= 1 ;
            swap( Son[v][0] , Son[v][1] ) ;
            swap( Lsum[v] , Rsum[v] ) ;
        }
        if ( SAME[v] <= 10000 ) {
            Val[v] = SAME[v] ;
            SAME[L] = SAME[R] = SAME[v] ;
            Sum[v] = SAME[v] * Size[v] ;
            MAXSUM[v] = max( SAME[v] , Sum[v] ) ;
            Lsum[v] = Rsum[v] = max( 0 , Sum[v] ) ;
            SAME[v] = 1e6 ;
        }
    }
    
    void Update( int now ) {
        int L = Son[now][0] ;
        int R = Son[now][1] ;
        Push( L ) , Push( R ) ;
        Size[now] = Size[L] + Size[R] + 1 ;
        Sum[now] = Sum[L] + Sum[R] + Val[now] ;
        Lsum[now] = max( Lsum[L] , Sum[L] + Val[now] + Lsum[R] ) ;
        Rsum[now] = max( Rsum[R] , Sum[R] + Val[now] + Rsum[L] ) ;
        MAXSUM[now] = max( max( MAXSUM[L] , MAXSUM[R] ) , Rsum[L] + Val[now] + Lsum[R] ) ;
    }
    
    int Build( int l , int r , int F ) {
        if ( l > r ) return 0 ;
        int mid = (l + r) / 2 ;
        int now = top ? S[top --] : ++ tot ;
        REV[now] = 0 ;
        Pre[now] = F ;
        SAME[now] = 1e6 ;
        Val[now] = Sum[now] = A[mid] ;
        Son[now][0] = Build( l , mid - 1 , now ) ;
        Son[now][1] = Build( mid + 1 , r , now ) ;
        Update( now ) ;
        return now ;
    }
    
    void Rotate( int x ) {
        int F = Pre[x] , G = Pre[F] ;
        int Side = ( Son[F][1] == x ) ;
        Pre[x] = G , Pre[F] = x ;
        Son[F][Side] = Son[x][!Side] ;
        Pre[Son[F][Side]] = F ;
        Son[G][Son[G][1]==F] = x ;
        Son[x][!Side] = F ;
        Update( F ) ;
        Update( x ) ;
    }
    
    void Splay( int x , int Goal ) {
        D[0] = 0 ;
        for (int Now = x ; Now ; Now = Pre[Now] ) D[++D[0]] = Now ;
        for (int i = D[0] ; i ; i -- ) Push( D[i] ) ;
        while ( Pre[x] != Goal ) {
            int y = Pre[x] , z = Pre[y] ;
            if ( z != Goal ) {
                if ( ( Son[y][0] == x ) ^ ( Son[z][0] == y ) ) Rotate( x ) ;
                else Rotate( y ) ;
            }
            Rotate( x ) ;
        }
        Update( x ) ;
        if ( !Goal ) Root = x ;
    }
    
    int Get( int x ) {
        for (int Now = Root ; x ; ) {
            Push( Now ) ;
            if ( x == Size[Son[Now][0]] + 1 ) return Now ;
            if ( x < Size[Son[Now][0]] + 1 ) Now = Son[Now][0] ;
            else x -= Size[Son[Now][0]] + 1 , Now = Son[Now][1] ;
        }
        return 0 ;
    }
    
    void GetRange( int l , int r ) {
        int x = Get(l) ;
        int y = Get(r) ;
        Splay( x , 0 ) ;
        Splay( y , Root ) ;
    }
    
    void Insert( int pos , int num ) {
        GetRange( pos + 1 , pos + 2 ) ;
        Son[Son[Root][1]][0] = Build( 1 , num , Son[Root][1] ) ;
        Update( Son[Root][1] ) ;
        Update( Root ) ;
    }
    
    void GetID( int x ) {
        if ( !x ) return ;
        S[++top] = x ;
        GetID( Son[x][0] ) ;
        GetID( Son[x][1] ) ;
    }
    
    void Delete( int l , int r ) {
        GetRange( l , r + 2 ) ;
        int R = Son[Root][1] ;
        GetID( Son[R][0] ) ;
        Pre[Son[R][0]] = 0 ;
        Son[R][0] = 0 ;
        Update( R ) ;
        Update( Root ) ;
    }
    
    void MAKE_SAME( int l , int r , int c ) {
        GetRange( l , r + 2 ) ;
        int L = Son[Son[Root][1]][0] ;
        SAME[L] = c ;
        Push( Son[Root][1] ) ;
        Update( Son[Root][1] ) ;
        Update( Root ) ;
    }
    
    void Reverse( int l , int r ) {
        GetRange( l , r + 2 ) ;
        int L = Son[Son[Root][1]][0] ;
        REV[L] ^= 1 ;
        Push( Son[Root][1] ) ;
        Update( Son[Root][1] ) ;
        Update( Root ) ;
    }
    
    int GET_SUM( int l , int r ) {
        GetRange( l , r + 2 ) ;
        int L = Son[Son[Root][1]][0] ;
        Push( L ) ;
        return Sum[L] ;
    }
    
    int main() {
        scanf( "%d%d" , &n , &m ) ;
        for (int i = 2 ; i <= n + 1 ; i ++ ) scanf( "%d" , &A[i] ) ;
        MAXSUM[0] = A[1] = A[n+2] = -1e6 ;
        Root = Build( 1 , n + 2 , 0 ) ;
        for (int i = 1 ; i <= m ; i ++ ) {
            scanf( "%s" , op + 1 ) ;
            if ( op[1] == 'I' ) {
                int pos , num ;
                scanf( "%d%d" , &pos , &num ) ;
                for (int i = 1 ; i <= num ; i ++ ) scanf( "%d" , &A[i] ) ;
                Insert( pos , num ) ;
            }
            if ( op[1] == 'D' ) {
                int pos , num ;
                scanf( "%d%d" , &pos , &num ) ;
                Delete( pos , pos + num - 1 ) ;
            }
            if ( op[1] == 'M' ) {
                if ( op[3] == 'K' ) {
                    int pos , num , c ;
                    scanf( "%d%d%d" , &pos , &num , &c ) ;
                    MAKE_SAME( pos , pos + num - 1 , c ) ;
                } else printf( "%d\n" , MAXSUM[Root] ) ;
            }
            if ( op[1] == 'R' ) {
                int pos , num ;
                scanf( "%d%d" , &pos , &num ) ;
                Reverse( pos , pos + num - 1 ) ;
            }
            if ( op[1] == 'G' ) {
                int pos , num ;
                scanf( "%d%d" , &pos , &num ) ;
                printf( "%d\n" , GET_SUM( pos , pos + num - 1 ) ) ;
            }
        }
        return 0 ;
    }
    

    以上.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值