题意:
给你一个数列,你需要对它进行如下操作:
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; }