[Submit][Status][Discuss]

Description

请写一个程序,要求维护一个数列,支持以下 6 种操作:
请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格
BZOJ1500 [NOI2005]维修数列(Splay tree)

Input

输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目。
第2行包含N个数字,描述初始时的数列。
以下M行,每行一条命令,格式参见问题描述中的表格。
任何时刻数列中最多含有500 000个数,数列中任何一个数字均在[-1 000, 1 000]内。
插入的数字总数不超过4 000 000个,输入文件大小不超过20MBytes。

Output

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

Sample Input

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

Sample Output

-1
10
1
10

HINT

 

BZOJ1500 [NOI2005]维修数列(Splay tree)

题解:Splay tree模板题;
自己的板子:
  1 #include<bits/stdc++.h>
  2 #define RI register int
  3 #define For(i,a,b) for (RI i=a;i<=b;++i)
  4 using namespace std;
  5 const int inf=0x3f3f3f3f;
  6 const int N=1e6+17;
  7 int n,m,rt,cnt;
  8 int a[N],id[N],fa[N],c[N][2];
  9 int sum[N],sz[N],v[N],mx[N],lx[N],rx[N];
 10 bool tag[N],rev[N];
 11 //tag表示是否有统一修改的标记,rev表示是否有统一翻转的标记
 12 //sum表示这个点的子树中的权值和,v表示这个点的权值
 13 //lx[]是一个子树以最左端为起点向右延伸的最大子串和,rx类似 
 14 //mx[]是当前子树的最大子串和
 15 queue<int> q;
 16 inline int read()
 17 {
 18     RI x=0,f=1;char ch=getchar();
 19     while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
 20     while('0'<=ch&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
 21     return x*f;
 22 }
 23 inline void pushup(RI x)
 24 {
 25     RI l=c[x][0],r=c[x][1];
 26     sum[x]=sum[l]+sum[r]+v[x];
 27     sz[x]=sz[l]+sz[r]+1;
 28     mx[x]=max(mx[l],max(mx[r],rx[l]+v[x]+lx[r]));
 29     lx[x]=max(lx[l],sum[l]+v[x]+lx[r]);
 30     rx[x]=max(rx[r],sum[r]+v[x]+rx[l]);
 31 }
 32 //上传记录标记
 33 inline void pushdown(RI x)
 34 {
 35     RI l=c[x][0],r=c[x][1];
 36     if(tag[x])
 37     {
 38         rev[x]=tag[x]=0;//我们有了一个统一修改的标记,再翻转就没有什么意义了
 39         if(l) tag[l]=1,v[l]=v[x],sum[l]=v[x]*sz[l];
 40         if(r) tag[r]=1,v[r]=v[x],sum[r]=v[x]*sz[r];
 41         if(v[x]>=0) 
 42         {
 43             if(l) lx[l]=rx[l]=mx[l]=sum[l];
 44             if(r) lx[r]=rx[r]=mx[r]=sum[r];
 45         }
 46         else
 47         {
 48             if(l) lx[l]=rx[l]=0,mx[l]=v[x];
 49             if(r) lx[r]=rx[r]=0,mx[r]=v[x];
 50         }
 51     }
 52     if(rev[x])
 53     {
 54         rev[x]=0;rev[l]^=1;rev[r]^=1;
 55         swap(lx[l],rx[l]);swap(lx[r],rx[r]);
 56         //注意,在翻转操作中,前后缀的最大和子序列都反过来了 
 57         swap(c[l][0],c[l][1]);swap(c[r][0],c[r][1]);
 58     }
 59 }
 60 inline void rotate(RI x,RI &k)
 61 {
 62     RI y=fa[x],z=fa[y],l=(c[y][1]==x),r=l^1;
 63     if (y==k)k=x;else c[z][c[z][1]==y]=x;
 64     fa[c[x][r]]=y;fa[y]=x;fa[x]=z;
 65     c[y][l]=c[x][r];c[x][r]=y;
 66     pushup(y);pushup(x);
 67     //旋转操作,一定要上传标记且顺序不能变 
 68 }
 69 inline void splay(RI x,RI &k)
 70 {
 71     while(x!=k)
 72     {
 73         int y=fa[x],z=fa[y];
 74         if(y!=k)
 75         {
 76             if((c[z][0]==y)^(c[y][0]==x)) rotate(x,k);
 77             else rotate(y,k);
 78         }
 79         rotate(x,k);
 80     }
 81 }
 82 //这是整个程序的核心之一,毕竟是伸展操作嘛
 83 inline int find(RI x,RI rk)
 84 {//返回当前序列第rk个数的标号 
 85     pushdown(x);
 86     RI l=c[x][0],r=c[x][1];
 87     if(sz[l]+1==rk) return x;
 88     if(sz[l]>=rk) return find(l,rk);
 89     else return find(r,rk-sz[l]-1);
 90 }
 91 inline void recycle(RI x)
 92 {//这就是用时间换空间的回收冗余编号机制,很好理解
 93     RI &l=c[x][0],&r=c[x][1];
 94     if(l) recycle(l);
 95     if(r) recycle(r);
 96     q.push(x);
 97     fa[x]=l=r=tag[x]=rev[x]=0;
 98 }
 99 inline int split(RI k,RI tot)//找到[k+1,k+tot]
100 {
101     RI x=find(rt,k),y=find(rt,k+tot+1);
102     splay(x,rt);splay(y,c[x][1]);
103     return c[y][0];
104 }
105 //这个split操作是整个程序的核心之三
106 //我们通过这个split操作,找到[k+1,k+tot],并把k,和k+tot+1移到根和右儿子的位置
107 //然后我们返回了这个右儿子的左儿子,这就是我们需要操作的区间
108 inline void query(RI k,RI tot)
109 {
110     RI x=split(k,tot);
111     printf("%d\n",sum[x]);
112 }
113 inline void modify(RI k,RI tot,RI val)//MAKE-SAME
114 {
115     RI x=split(k,tot),y=fa[x];
116     v[x]=val;tag[x]=1;sum[x]=sz[x]*val;
117     if(val>=0) lx[x]=rx[x]=mx[x]=sum[x];
118         else lx[x]=rx[x]=0,mx[x]=val;
119     pushup(y);pushup(fa[y]);
120     //每一步的修改操作,由于父子关系发生改变
121     //及记录标记发生改变,我们需要及时上传记录标记
122 }
123 inline void rever(RI k,RI tot)//翻转 
124 {
125     RI x=split(k,tot),y=fa[x];
126     if(!tag[x])
127     {
128         rev[x]^=1;
129         swap(c[x][0],c[x][1]);
130         swap(lx[x],rx[x]);
131         pushup(y);pushup(fa[y]);
132     }
133     //同上
134 }
135 inline void erase(RI k,RI tot)//DELETE
136 {
137     RI x=split(k,tot),y=fa[x];
138     recycle(x);c[y][0]=0;
139     pushup(y);pushup(fa[y]);
140     //同上
141 }
142 inline void build(RI l,RI r,RI f)
143 {
144     RI mid=(l+r)>>1,now=id[mid],pre=id[f];
145     if(l==r)
146     {
147         mx[now]=sum[now]=a[l];
148         tag[now]=rev[now]=0;
149         //这里这个tag和rev的清0是必要,因为这个编号可能是之前冗余了
150         lx[now]=rx[now]=max(a[l],0);
151         sz[now]=1;
152     }
153     if(l<mid) build(l,mid-1,mid);
154     if(mid<r) build(mid+1,r,mid);
155     v[now]=a[mid]; fa[now]=pre;
156     pushup(now); //上传记录标记
157     c[pre][mid>=f]=now;
158     //当mid>=f时,now是插入到又区间取了,所以c[pre][1]=now,当mid<f时同理
159 }
160 inline void insert(RI k,RI tot)
161 {
162     for(int i=1;i<=tot;++i) a[i]=read();
163     for(int i=1;i<=tot;++i)
164     {
165         if(!q.empty()) id[i]=q.front(),q.pop();
166         else id[i]=++cnt;//利用队列中记录的冗余节点编号
167     }
168     build(1,tot,0);
169     RI z=id[(1+tot)>>1];
170     RI x=find(rt,k+1),y=find(rt,k+2);
171      //首先,依据中序遍历,找到我们需要操作的区间的实际编号
172     splay(x,rt);splay(y,c[x][1]);
173     //把k+1(注意我们已经右移了一个单位)和(k+1)+1移到根和右儿子
174     fa[z]=y;c[y][0]=z;
175     //直接把需要插入的这个平衡树挂到右儿子的左儿子上去就好了
176     pushup(y);pushup(x);
177     //上传记录标记
178 }
179 //可以这么记,只要用了split就要重新上传标记
180 //只有find中需要下传标记
181 int main()
182 {
183     n=read(),m=read();
184     mx[0]=a[1]=a[n+2]=-inf;
185     For(i,1,n) a[i+1]=read();
186     For(i,1,n+2) id[i]=i;//虚拟了两个节点1和n+2,然后把需要操作区间整体右移一个单位
187     build(1,n+2,0);//建树
188     rt=(n+3)>>1;cnt=n+2;//取最中间的为根
189     RI k,tot,val;char ch[10];
190     while(m--)
191     {
192         scanf("%s",ch);
193         if(ch[0]!='M' || ch[2]!='X') k=read(),tot=read();
194         if(ch[0]=='I') insert(k,tot);
195         if(ch[0]=='D') erase(k,tot);//DELETE
196         if(ch[0]=='M')
197         {
198             if(ch[2]=='X') printf("%d\n",mx[rt]);//MAX-SUM
199             else val=read(),modify(k,tot,val);//MAKE-SAME
200         }
201         if(ch[0]=='R') rever(k,tot);//翻转 
202         if(ch[0]=='G') query(k,tot);//GET-SUM
203     }
204     return 0;
205 }
View Code

 

相关文章: