• NOI2005 维护数列 lg2042


     这道题据说是noi题目中算是比较毒瘤的数据结构题了,5k多的代码加上随手写挂细节,我调了两天

    题面见https://www.luogu.org/problemnew/show/P2042

    (歪个题,这类区间操作的数据结构题可以先去写GSS系列题,会比较容易理解合并的操作)

    (再歪个题,我将在近期补一篇博客说一下自己区间树的理解)

    一共维护6个操作

    第一个操作为插入一棵新的子树,因为是插子树,所以单点插是要t飞的,那么就考虑一个优化,先建好树再挂到他应该出现的位置上

    复杂度从nlogn降到了n。

    第二个操作删除就把l-1,r+1分别转到根和根的左儿子,那么根的左儿子的右儿子就是题目中需要处理的区间了,直接递归删就完事了

    第三个操作翻转就是文艺平衡树里的那样了

    第四个区间覆盖的思路也和操作二类似,把需要操作的区间专门转出来,然后打个lazy_tag就好了

    第五个操作也同上,把区间转出来之后输出一下sum

    第六个操作就直接查询根节点信息里的区间最大值就好了

    操作拆开考虑是都不难,但揉在一起的时候就麻了,希望大家能顺利写出这道题。

    #include<bits/stdc++.h>
    #include<queue>
    using namespace std;
    #define INF 1000000000
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    int n,m,cnt,root,a[1000010],id[1000010];
    queue<int> q;
    struct node{
        int ch[2],sum,cnt,val,f,size,lmax,rmax,maxx;
        bool cov,rev;
    }st[1000010];
    
    inline bool identify(int p){
        return st[st[p].f].ch[1]==p;
    }//认定自己是父亲的左儿子还是右儿子 
    
    inline void connect(int x,int fa,int son){
        st[x].f=fa;st[fa].ch[son]=x;return;
    }//连接操作 
    
    inline void push_up(int x){//上推操作 
        int ls=st[x].ch[0];int rs=st[x].ch[1];
        st[x].size=st[ls].size+st[rs].size+1;//该子树的大小为左右子树大小之和加上一 
        st[x].sum=st[ls].sum+st[rs].sum+st[x].val;//子树的权值和等于左右子树的和加上当前点的权值 
        st[x].maxx=max(st[ls].rmax+st[x].val+st[rs].lmax,max(st[ls].maxx,st[rs].maxx));//最大值,左端最大值,右端最大值,记住要加上当前点的权值,其它就是常规操作 
        st[x].lmax=max(st[ls].lmax,st[ls].sum+st[x].val+st[rs].lmax);
        st[x].rmax=max(st[rs].rmax,st[rs].sum+st[x].val+st[ls].rmax);
    } 
    inline void push_down(int x){//下移标记,这个操作我写挂了两次,头麻 
        int ls=st[x].ch[0];int rs=st[x].ch[1];
        if(st[x].cov){//如果有覆盖操作,翻转就没用了 
            st[x].cov=st[x].rev=0;
            if(ls) st[ls].val=st[x].val,st[ls].cov=true,st[ls].sum=st[ls].size*st[ls].val;//有左儿子的话下推标记 
            if(rs) st[rs].val=st[x].val,st[rs].cov=true,st[rs].sum=st[rs].size*st[rs].val;//有右儿子的话下推标记 
            if(st[x].val>=0){
                if(ls) st[ls].lmax=st[ls].rmax=st[ls].maxx=st[ls].sum; 
                if(rs) st[rs].lmax=st[rs].rmax=st[rs].maxx=st[rs].sum;
            } 
            else{
                if(ls) st[ls].lmax=st[ls].rmax=0,st[ls].maxx=st[ls].val;
                if(rs) st[rs].lmax=st[rs].rmax=0,st[rs].maxx=st[rs].val;
            }
        }
        /*
        下推标记其实没有什么亮点,就是写起来比较长然后容易写挂细节
        这里着重解释一下为什么左右max可以为0,而区间max一定要选择一个值
        因为区间合并的时候需要用左右儿子的左右端的最大值,值设为0的意思就是不选择这个区间里的任何东西
        如果是写过线段树上维护区间的人可能会想既然不选的东西,值设成负的有什么大不了呢,可这里就有一个
        不大一样的操作,左右子树信息上推的时候要加上当前节点的值,如果不把lmax和rmax设成0,就要分多种情况讨论
        这代码本来写起来就麻烦了,再分类讨论......所以,当前节点的值若小于0的时候,lmax和rmax就设为0 
        */
        if(st[x].rev){
            st[x].rev^=1;st[ls].rev^=1;st[rs].rev^=1;
            swap(st[ls].lmax,st[ls].rmax);swap(st[rs].lmax,st[rs].rmax);
            swap(st[ls].ch[0],st[ls].ch[1]);swap(st[rs].ch[0],st[rs].ch[1]);//翻转的时候记得换儿子 
        }
    } 
    inline void rotate(int x){//splay的常规操作就没啥好说的了 
        int y=st[x].f;int z=st[y].f;
        int yson=identify(x);int zson=identify(y);
        int b=st[x].ch[yson^1];
        connect(b,y,yson);connect(y,x,(yson^1));connect(x,z,zson);
        push_up(y);push_up(x);return;
    }
    
    inline void splay(int x,int goal){
        while(st[x].f!=goal){
            int y=st[x].f;int z=st[y].f;
            int yson=identify(x);int zson=identify(y);
            if(z!=goal){
                if(yson==zson) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
        if(!goal) root=x;
        return;
    }
    
    inline int find(int p,int rk){//此处运用了一个区间树查询的思想,我会在近期写一份关于区间树的博客,请期待(逃 
        push_down(p);
        int ls=st[p].ch[0];int rs=st[p].ch[1];
        if(st[ls].size+1==rk) return p;
        if(st[ls].size>=rk) return find(ls,rk);
        return find(rs,rk-st[ls].size-1);
    }
    
    inline void recycle(int p){//这辣鸡题卡内存,那么就废物利用呗 
        if(!p) return;
        int ls=st[p].ch[0];int rs=st[p].ch[1];
        recycle(ls);recycle(rs);q.push(p);
        st[p].ch[0]=st[p].ch[1]=st[p].f=0;
        st[p].rev=st[p].cov=0;return;
    }
    
    inline int split(int k,int tot){//这个操作就找到自己要修改的区间端点的l-1和r+1,分别旋转到根和根的右儿子 
        int x=find(root,k);int y=find(root,k+tot+1);//那么根的右儿子的左儿子就是当前要操作的区间了 
        splay(x,0);splay(y,x);return st[y].ch[0];//这个和文艺平衡树的操作类似,可以参考那份代码进行理解 
    }//最后返回一下当前区间子树的根节点 
    
    inline void query(int k,int tot){
        int x=split(k,tot); 
        printf("%d
    ",st[x].sum);return;
    }
    
    inline void cov(int k,int tot,int val){
        int x=split(k,tot);int y=st[x].f;
        st[x].val=val;st[x].cov=true;st[x].sum=st[x].size*val;
        if(val>=0){st[x].lmax=st[x].rmax=st[x].maxx=st[x].sum;}
        else{st[x].lmax=st[x].rmax=0;st[x].maxx=val;}
        push_up(y);push_up(st[y].f);return;
    }//区间覆盖区间翻转道理都差不多 
    //我就解释一下区间覆盖把,区间覆盖的时候,当前值改一下,lazy_tag改一下,相应的最大值总和改一下,上推一下,它就很合理...... 
    inline void rev(int k,int tot){
        int x=split(k,tot);
        int y=st[x].f;int z=st[y].f;
        if(!st[x].cov){
            st[x].rev^=1;swap(st[x].ch[0],st[x].ch[1]);
            swap(st[x].lmax,st[x].rmax);
            push_up(y);push_up(st[y].f);
        }
    }
    
    inline void erase(int k,int tot){
        int x=split(k,tot);int y=st[x].f;
        recycle(x);st[y].ch[0]=0;
        push_up(y);push_up(st[y].f);return;
    }//区间删除 
    
    inline void build(int l,int r,int f){
        if(l>r) return;
        int mid=(l+r)>>1;int now=id[mid];int last=id[f];
        if(l==r){
            st[now].sum=a[l];st[now].size=1;
            st[now].rev=st[now].cov=0;
            if(a[l]>=0){st[now].lmax=st[now].rmax=st[now].maxx=a[l];}
            else{st[now].lmax=st[now].rmax=0;st[now].maxx=a[l];}
        }
        else build(l,mid-1,mid),build(mid+1,r,mid);
        st[now].val=a[mid];st[now].f=last;push_up(now);
        st[last].ch[mid>=f]=now;return;
    }//这个操作它对每个节点进行标号并依照这个进行建树 
    
    inline void insert(int k,int tot){
        for(int i=1;i<=tot;i++) a[i]=read();
        for(int i=1;i<=tot;i++){
            if(!q.empty()){id[i]=q.front();q.pop();}
            else id[i]=++cnt;
        }
        build(1,tot,0);int z=id[(1+tot)>>1];
        int x=find(root,k+1);int y=find(root,k+2);
        splay(x,0);splay(y,x);
        st[z].f=y;st[y].ch[0]=z;
        push_up(y);push_up(x);return; 
    }//在插入一棵新的子树的时候,可以先把这棵子树建出来,然后往原树上一挂,美滋滋 
    int main(){
        n=read();m=read();int i,j,k;//记得给根节点赋极小值 
        st[0].maxx=-INF;memset(a,-0x3f,sizeof(a));
        for(i=1;i<=n;i++) a[i+1]=read();
        for(i=1;i<=n+2;i++) id[i]=i;
        build(1,n+2,0);cnt=n+2;root=(n+3)>>1;
        int tot,val;
        char ch[10];
        while(m--)
        {//操作跟着题目要求走就好了 
            scanf("%s",ch);
            if(ch[0]!='M'||ch[2]!='X')k=read(),tot=read();
            if(ch[0]=='I') insert(k,tot);
            if(ch[0]=='D') erase(k,tot);
            if(ch[0]=='M')
            {
                if(ch[2]=='X')printf("%d
    ",st[root].maxx);
                else val=read(),cov(k,tot,val);
            }
            if(ch[0]=='R')rev(k,tot);
            if(ch[0]=='G')query(k,tot);
        }
        return 0;
    }
  • 相关阅读:
    做一名让开发佩服的测试(浅析)
    软件测试行业—职业发展之路(浅谈)
    移动端APP测试总结
    Http协议!(转)
    移动App书写Test Case时需要考虑的检查点
    黑盒测试方法——边界值分析(转)
    黑盒测试方法——等价类划分(转)
    在线网络实用工具
    Android Monkey压力测试(转)
    软件工程(浅谈)
  • 原文地址:https://www.cnblogs.com/wenci/p/10133645.html
Copyright © 2020-2023  润新知