维护数列 洛谷2042 bzoj1500 NOI2005

题意:

给你一个数列,你需要对它进行如下操作:

1.在某一个数后面插入连续的一些数

2.从某个数开始连续删除一些数

3.把从某个数开始的连续的一些数全部变为x

4.区间翻转

5.区间求和

6.区间最大子列

由于要区间翻转,所以建两个虚拟节点1号节点和n+2号节点,以便区间翻转。

插入连续的区间的话可以先把这个序列建成一棵平衡的树,再把k+1旋转到根,原来的k+2变为k+1的右子树,然后直接把新建好的树挂到k+2的左子树上就行了。

区间修改和区间翻转都是打标机,和线段树差不多,有区间修改的标记就没必要再执行区间翻转了。

求和和最大子列的维护方式也是和线段树差不多,就是除了左子树右子树,还要看这个节点本身的那个值对结果的影响。

最后就是,这个题的空间很小,要回收空间。

最后粘代码

//本题从洛谷神犇  I_AM_HelloWord 处学来 代码部分修改 
#include <bits/stdc++.h>
using namespace std;

int n,m,fa[1000001],rt,cnt,a[1000001],id[1000001],c[1000001][2];
//a记录权值,id是节点编号 
int sum[1000001],sz[1000001],v[1000001],mx[1000001],lx[1000001],rx[1000001];
//v是当前点的权值,mx是当前点的最大子列和,lx是左侧最大子列,rx是右侧
//注意:mx不可以为0,lx和rx是可以为0的,因为mx直接作为最终答案输出 
queue <int> q;//记录可以被回收的点的id 
int tag[1000001],rev[1000001];
void pushup(int x)
{
    int l=c[x][0],r=c[x][1];
    sum[x]=sum[l]+v[x]+sum[r];
    sz[x]=sz[l]+sz[r]+1;
    mx[x]=max(mx[l],max(mx[r],rx[l]+v[x]+lx[r]));
    lx[x]=max(lx[l],sum[l]+v[x]+lx[r]);
    rx[x]=max(rx[r],sum[r]+v[x]+rx[l]);
}
void build(int l,int r,int f)//f为当前点的父节点 
{
    int mid=(l+r)>>1,now=id[mid],pre=id[f];
    
    if(l==r)
    {
        mx[now]=sum[now]=a[l];
        tag[now]=rev[now]=0;
//这里这个tag和rev的清0是必要的,因为这个编号可能是之前删除过又拿出来用的
//虽然似乎清零过一遍了 
        lx[now]=rx[now]=max(a[l],0);
        sz[now]=1;		
    }
    if(l<mid)
    build(l,mid-1,mid);
    if(r>mid)
    build(mid+1,r,mid);
    v[now]=a[mid];
    fa[now]=pre;
    pushup(now);
    c[pre][mid>=f]=now;
//当mid>=f时,now是插入到右区间去了,所以c[pre][1]=now,当mid<f时同理	
}
void pushdown(int x)
{
    int l=c[x][0],r=c[x][1];
    if(tag[x])
    {
        rev[x]=tag[x]=0;//我们有了一个统一修改的标记,再翻转就没有什么意义了
        if(l)
        {
            tag[l]=1;
            v[l]=v[x];
            sum[l]=v[x]*sz[l];
        }
        if(r)
        {
            tag[r]=1;
            v[r]=v[x];
            sum[r]=v[x]*sz[r];
        }
        if(v[x]>=0)
        {
            if(l)
            lx[l]=rx[l]=mx[l]=sum[l];
            if(r)
            lx[r]=rx[r]=mx[r]=sum[r];
        }
        else
        {
            if(l)
            {
                lx[l]=rx[l]=0;
                mx[l]=v[x];
            }
            if(r)
            {
                lx[r]=rx[r]=0;
                mx[r]=v[x];
            }
        }
    }
    if(rev[x])
    {
        rev[x]=0;
        rev[l]^=1;
        rev[r]^=1;
        swap(lx[l],rx[l]);
        swap(lx[r],rx[r]);
        swap(c[l][0],c[l][1]);
        swap(c[r][0],c[r][1]);
    }
}
int find(int x,int rk)
{
    pushdown(x);
    int l=c[x][0],r=c[x][1];
    if(sz[l]+1==rk)
    return x;
    if(sz[l]>=rk)
    return find(l,rk);
    else
    return find(r,rk-sz[l]-1);
}
void rotate(int x,int &k)
{
    int y=fa[x],z=fa[y],l,r;
    if(c[y][0]==x)
    l=0;
    else
    l=1;
    r=l^1;
    if(y==k)
    k=x;//对k的真实改变 
    else if(c[z][0]==y)
    c[z][0]=x;
    else
    c[z][1]=x;
    fa[x]=z;
    fa[y]=x;
    fa[c[x][r]]=y;
    c[y][l]=c[x][r];
    c[x][r]=y;
    pushup(y);//注意顺序 
    pushup(x);
}
/*
inline void rotate(int x,int &k){
    int y=fa[x],z=fa[y],l=(c[y][1]==x),r=l^1;
    if (y==k)k=x;else c[z][c[z][1]==y]=x;
    fa[c[x][r]]=y;fa[y]=x;fa[x]=z;
    c[y][l]=c[x][r];c[x][r]=y;
    pushup(y);pushup(x);
    //旋转操作,一定要上传记录标记
}*/
void splay(int x,int &k)//k是真实改变的 
{
    while(x!=k)
    {
        int y=fa[x],z=fa[y];
        if(y!=k)
        {
            if(c[z][0]==y ^ c[y][0]==x)
            rotate(x,k);
            else
            rotate(y,k);
        }
        rotate(x,k);
    }
}
void insert(int k,int tot)
{
    for(int i=1;i<=tot;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=tot;i++)
    {
        if(!q.empty())
        {
            id[i]=q.front();
            q.pop();
        }	
        else
        id[i]=++cnt;
    }
    build(1,tot,0);//将读入的tot个数建成一颗平衡树
    int z=id[(1+tot)>>1];//取中点为根,让整棵树建好后尽可能平衡
    int x=find(rt,k+1),y=find(rt,k+2);
    //把k+1(注意我们已经右移了一个单位)和(k+1)+1移到根和右儿子
    splay(x,rt);
    splay(y,c[x][1]);
    fa[z]=y;
    c[y][0]=z;//直接把需要插入的这个平衡树的根挂到右儿子的左儿子上去就好了
    pushup(y);
    pushup(x); 
}
//我们通过这个split操作,找到[k+1,k+tot],并把k,和k+tot+1移到根和右儿子的位置
//然后我们返回了这个右儿子的左儿子,这就是我们需要操作的区间
int split(int k,int tot)
{
    int x=find(rt,k),y=find(rt,k+tot+1);
    //find应该没错 
//	cout<<rt<<endl;
    splay(x,rt);
    splay(y,c[x][1]);
    return c[y][0];
}
void recycle(int x)
{
    int &l=c[x][0],&r=c[x][1];
    if(l)
    recycle(l);
    if(r)
    recycle(r);
    q.push(x);
    fa[x]=l=r=tag[x]=rev[x]=0;
}
void erase(int k,int tot)
{
    int x=split(k,tot),y=fa[x];
    recycle(x);
    c[y][0]=0;//直接把这棵子树断开连接 
    pushup(y);
    pushup(fa[y]);
}
void modify(int k,int tot,int val)
{
    int x=split(k,tot),y=fa[x];
    v[x]=val;
    tag[x]=1;
    sum[x]=sz[x]*val;
    if(val>=0)
    lx[x]=rx[x]=mx[x]=sum[x];
    else
    {
        lx[x]=rx[x]=0;
        mx[x]=v[x];
    }
    pushup(y);
    pushup(fa[y]);
}
void rever(int k,int tot)
{
    int x=split(k,tot),y=fa[x];
    if(!tag[x]) 
    {
        rev[x]^=1;
        swap(c[x][0],c[x][1]);
        swap(lx[x],rx[x]);
        pushup(y);
        pushup(fa[y]);
    }
}
//split错了 
void query(int k,int tot)
{
    int x=split(k,tot);
//	cout<<x<<endl;
    printf("%d\n",sum[x]);
}
int main()
{
    scanf("%d%d",&n,&m);
    mx[0]=a[1]=a[n+2]=-2e9;
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i+1]);
//虚拟了两个节点1和n+2,然后把需要操作区间整体右移一个单位
    for(int i=1;i<=n+2;i++)
    id[i]=i;
    build(1,n+2,0);//建树
    rt=(n+3)>>1;
    cnt=n+2; 
    int k,tot,val;
    char s[10];
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        if(s[0]!='M'||s[2]!='X')
        scanf("%d%d",&k,&tot);
        if(s[0]=='I')
        insert(k,tot);
        if(s[0]=='D')
        erase(k,tot);
        if(s[0]=='M')
        {
            if(s[2]=='X')
            printf("%d\n",mx[rt]);
            else
            {
                scanf("%d",&val);
                modify(k,tot,val);
            }			
        }
        if(s[0]=='R')
        rever(k,tot);
        if(s[0]=='G')
        query(k,tot);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/79787797
今日推荐