题解:[NOI2005] 维护数列

题解:数列 | 洛谷P2402,P2710

Posted by TH911 on January 21, 2025

考虑到P2710 数列P2042 [NOI2005]维护数列的区别仅仅是一个可以被转换为其他操作的操作和数组大小,放到一篇。

题意分析

需要维护的操作有:

  1. 区间插入
  2. 区间删除
  3. 区间翻转
  4. 区间覆盖
  5. 区间求和
  6. 单点查询
  7. 求区间最大子段和

我们可以通过平衡树 FHQ Treap 来维护区间信息。

FHQ Treap 维护区间信息见 FHQ Treap 之区间操作

以下代码基于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct node{
    int left,right;
    int value,rand,size;
    //maxsum:最大子段和
    //pre/suf:子树区间的最大前缀/后缀和,可以为0
    int set,sum,maxsum,pre,suf;
    bool reverse;
    node(){
        set=114514;
    }
    void clear(){
        left=right=value=rand=size=set=sum=maxsum=pre=suf=0;
        reverse = false;
    }
}t[N+1];

区间插入、删除

FHQ Treap 分裂后操作即可。

注意 INSERT 操作时最好不要将整棵树分裂 $n$ 次再合并 $n$ 次,因为这样常数较大。

建议先将 $n$ 个数建一棵树,然后再合并到整棵树上。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
void insert(int x,int n){
    int pl;
    scanf("%d",&pl);
    int p=create(pl);
    for(int i=2;i<=n;i++){
        scanf("%d",&pl);
        p=merge(p,create(pl));
    }
    int l,r;
    split(root,x,l,r);
    root=merge(merge(l,p),r);
}

区间翻转

打标记 t[i].reverse 即可。

警告

更新 reverse 时,需要交换左右子节点和前后缀最大和(见下文)。

区间覆盖

打标记 t[i].set 传递即可。

注意:set 不能设为 $0$,请设置为值域 $[-10^3,10^3]$ 之外。

我设置的 114514

区间求和

维护标记 t[i].sum

求区间最大子段和

维护标记 t[i].maxsum

标记下传与更新上传

更新上传:$up()$

首先明确需要更新的信息:sizesummaxsumpresuf

size 明显是左右子节点的 size 加 $1$。

sum 是左右子节点的 sum 加自己的权值。

如图,pre 分两种情况:

pre 有可能不过当前节点 t[p],那么就是左子树区间的 pre,即 t[t[p].left].pre。或者经过 t[i],那么就是整个左子树区间的和(sum)加上自己的权值(value)再加右子树区间的最大 pre

可能会存在不选择右子树区间的 pre 的情况,因此我们pre 与 $0$ 取 max即可。当 $pre>0$ 的时候,明显选择会比不选更优。

即:

1
t[p].pre = max(t[t[p].left].pre,t[t[p].left].sum + t[p].value + t[t[p].right].pre,0);

同理:

1
t[p].suf = max(t[t[p].right].suf,t[t[p].left].suf + t[p].value + t[t[p].right].sum,0);

类似的,maxsum 要么不过当前节点:

1
max(t[t[p].left].maxsum,t[t[p].right].maxsum);

要么就是:

1
t[p].maxsum = t[t[p].left].suf + t[p].value + t[t[p].right].pre;

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void up(int p){
	/*
	int left,right;
	int value,rand,size;
	int set,sum,maxsum,pre,suf;
	*/
	
	t[0].clear();
	int &left = t[p].left;
	int &right = t[p].right;
	
	t[p].size = t[left].size + 1 + t[right].size;
	t[p].sum = t[left].sum + t[p].value + t[right].sum;
	t[p].pre = max(t[left].pre,t[left].sum + t[p].value + t[right].pre,0);
	t[p].suf = max(t[right].suf,t[left].suf + t[p].value + t[right].sum,0);
	t[p].maxsum = t[left].suf + t[p].value + t[right].pre;
	if(left){
		t[p].maxsum = max(t[p].maxsum,t[left].maxsum);
	} 
	if(right){
		t[p].maxsum = max(t[p].maxsum,t[right].maxsum);
	}
}

标记下传:$down()$

我们一共打了两个懒标记:setreverse

首先对于 reverse,很好理解,左右子节点的 reverse 分别取反后交换其左右子节点presuf 即可。

警告

请不要忘记交换 presuf,因为区间翻转了,前、后缀也会交换,因此最大和也应当交换。

对于 set需要在下传之前判断子节点是否存在,不存在则不下传

然后记得 presuf 可以取 $0$。

注意 set 要清空为一个值域之外的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void down(int p){
    t[0].clear();
    int &left = t[p].left;
    int &right = t[p].right;
    if(t[p].set != 114514){

        if(left){
            t[left].sum = t[left].size * t[p].set;
            t[left].maxsum = max(t[p].set,t[left].sum);
            t[left].pre = max(t[left].sum,0);
            t[left].suf = max(t[left].sum,0);
            t[left].value = t[p].set;
            t[left].set = t[p].set;
        }
        if(right){
            t[right].sum = t[right].size * t[p].set;
            t[right].maxsum = max(t[p].set,t[right].sum);
            t[right].pre = max(t[right].sum,0);
            t[right].suf = max(t[right].sum,0);
            t[right].value = t[p].set;
            t[right].set = t[p].set;
        }
        t[p].set=114514;
    }if(t[p].reverse){
        if(left){
            t[left].reverse = !t[left].reverse;
            swap(t[left].left,t[left].right);
            swap(t[left].pre,t[left].suf);
        } 
        if(right){
            t[right].reverse = !t[right].reverse;
            swap(t[right].left,t[right].right);
            swap(t[right].pre,t[right].suf);
        }
        t[p].reverse = false;
    }
    t[0].clear();
}

内存回收

需要回收,因为保证数列中的数的数量不超过 $200000$,而直接开最大为 $N+M\times 1000=2(10^7+10^5)$,会导致 $\text{MLE}$。

封装一个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct mem{
    vector<int>q;
    mem(){
        q.resize(0);
    }
    int ask(){
        if(q.size()){
            int x=q.back();
            q.pop_back();
            return x;
        }else{
            static int top;
            return ++top;
        }
    }
    void remove(int p){
        q.push_back(p);
    }
}mem;

需要分配内存时,调用 mem.ask() 即可。

具体操作

没什么好说的

注意事项

  • 维护的 presuf 可以为 $0$,无论是创建节点更新上传还是标记下传,都需要和 $0$ 取 max。
  • 维护的 maxsum 不能为 $0$,即连续子序列不能为空。
  • 翻转区间时需要交换 presuf 值。
  • 随时清空 $0$ 号节点,不然就处处判断是否有子节点。

AC 代码

保留了调试的注释让你们看看我调了 7h 的结果。

P2710 数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath> 
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<random>
#define cerr cout
//#define int long long
using namespace std;
typedef long long ll;
mt19937 Rand(123456/*time(0)*/);
const int N=2e5;
int max(int a,int b){
	return (a>b?a:b);
}
int max(int a,int b,int c){
	return (a>b?(a>c?a:c):(b>c?b:c));
}
int root;
struct treap{
	struct node{
		int left,right;
		int value,rand,size;
		//pre/suf:node所在子树对应的区间的前/后缀最大和. 
		int set,sum,maxsum,pre,suf;
		bool reverse;
		node(){
			set=114514;
		}
		void clear(){
			left=right=value=rand=size=set=sum=maxsum=pre=suf=0;
			reverse = false;
		}
	}t[N+1];
	
	struct mem{
		vector<int>q;
		mem(){
			q.resize(0);
		}
		int ask(){
			if(q.size()){
				int x=q.back();
				q.pop_back();
				return x;
			}else{
				static int top;
				return ++top;
			}
		}
		void remove(int p){
			q.push_back(p);
		}
	}mem;
	
	void up(int p){
		/*
		int left,right;
		int value,rand,size;
		int set,sum,maxsum,pre,suf;
		*/
		
		t[0].clear();
//		//cerr<<"::"<<t[0].left<<endl;
//		t[0].maxsum = -2147483647/2;
		//cerr<<"   p=   "<<p<<endl;
		int &left = t[p].left;
		int &right = t[p].right;
		
		t[p].size = t[left].size + 1 + t[right].size;
		t[p].sum = t[left].sum + t[p].value + t[right].sum;
		/*t[p].maxsum = max(t[left].maxsum,t[left].suf + t[p].value + t[right].pre,t[right].maxsum);
		t[p].maxsum = max(t[p].maxsum,t[left].suf + t[p].value,t[p].value + t[right].pre);*/
		t[p].pre = max(t[left].pre,t[left].sum + t[p].value + t[right].pre,0);
		t[p].suf = max(t[right].suf,t[left].suf + t[p].value + t[right].sum,0);
		
		t[p].maxsum = t[left].suf + t[p].value + t[right].pre;
		
		if(left){
			t[p].maxsum = max(t[p].maxsum,t[left].maxsum);
		} 
		if(right){
			t[p].maxsum = max(t[p].maxsum,t[right].maxsum);
		}
		
//		if(left && right){
//			//cerr<<"1:\n";
//			t[p].maxsum = max(t[p].maxsum,t[left].maxsum,t[right].maxsum);
//			t[p].maxsum = max(t[p].maxsum,t[left].suf + t[p].value + t[right].pre);
//			//cerr<<"t["<<p<<"].maxsum = max("<<t[left].maxsum<<","<<t[left].suf + t[p].value + t[right].pre<<","<<t[right].maxsum<<endl;
//			t[p].maxsum = max(t[p].maxsum,t[left].suf + t[p].value,t[p].value + t[right].pre);
//			//cerr<<"t["<<p<<"].maxsum = max("<<t[p].maxsum<<","<<t[left].suf + t[p].value<<","<<t[p].value + t[right].pre<<endl;
//		}else if(left){
//			//cerr<<"2:\n";
//			//cerr<<"t[left].suf = "<<"t["<<left<<"].suf"<<" = "<<t[left].suf<<endl;
//			t[p].maxsum = max(t[p].maxsum,t[left].maxsum,t[left].suf + t[p].value);
//			//cerr<<"t["<<p<<"].maxsum = max("<<t[left].maxsum<<","<<t[left].suf + t[p].value<<endl;
//		}else if(right){
//			//cerr<<"3:\n";
//			t[p].maxsum = max(t[p].maxsum,t[p].value + t[right].pre,t[right].maxsum);
//			//cerr<<"t["<<p<<"].maxsum = max("<<t[p].value + t[right].pre<<","<<t[right].maxsum<<endl;
//		}
		
//		t[0] = {};
//		int &left = t[p].left;
//		int &right = t[p].right;
//		t[p].size = 1;
//		t[p].sum = t[p].value;
//		t[p].maxsum = t[p].value;
//		if(left){
//			t[p].size += t[left].size;
//			t[p].sum += t[left].sum;
//			t[p].maxsum = max(t[p].maxsum,t[left].maxsum,t[left].suf + t[p].value);
//			if(right){
//				t[p].pre = max(t[left].pre,t[left].sum + t[p].value + t[right].pre,t[left].sum + t[p].value);
//			}else{
//				t[p].pre = max(t[left].pre,t[left].sum + t[p].value);
//			}
////			if(right){
////				t[p].suf = max(t[p].suf,t[left].suf + t[p].value + t[right].sum)
////			}else{
////				t[p].suf = max(t[p].value,t[left].suf + t[p].value);
////			}
//		}
//		if(right){
//			t[p].size += t[right].size;
//			t[p].sum += t[right].sum;
//			t[p].maxsum = max(t[p].maxsum,t[right].maxsum,t[p].value + t[right].pre);
//			if(left){
//				t[p].suf = max(t[right].suf,t[left].suf + t[p].value + t[right].sum,t[p].value + t[right].sum);
//			}else{
//				t[p].suf = max(t[right].suf,t[p].value + t[right].sum);
//			}
//		}
//		if(left && right){
//			t[p].maxsum = max(t[p].maxsum,t[left].maxsum + t[p].value + t[right].maxsum);
//			t[p].pre = max(t[p].pre,t[left].sum + t[p].value + t[right].pre);
//		}
//		cerr<<"t["<<p<<"].maxsum = max("<<t[t[p].left].maxsum<<","<<t[t[p].right].maxsum<<","<<t[t[p].left].suf+t[p].value+t[t[p].right].pre<<")\n";;
//		t[p].pre = max(t[t[p].left].pre,t[t[p].left].sum+t[p].value+t[t[p].right].pre);
//		t[p].suf = max(t[t[p].right].suf,t[t[p].left].suf+t[p].value+t[t[p].right].sum); 
	}//不一定记得在推平后删除翻转标记! 
	void down(int p){
//		if(p==0){
//			return;
//		} 
		
		t[0].clear();
		int &left = t[p].left;
		int &right = t[p].right;
		if(t[p].set != 114514){
			
			if(left){
				t[left].sum = t[left].size * t[p].set;
				t[left].maxsum = max(t[p].set,t[left].sum);
				t[left].pre = max(t[left].sum,0);
				t[left].suf = max(t[left].sum,0);
				t[left].value = t[p].set;
				t[left].set = t[p].set;
//				cerr<<"t["<<left<<"].value="<<t[p].set<<endl;
			}
			if(right){
				t[right].sum = t[right].size * t[p].set;
				t[right].maxsum = max(t[p].set,t[right].sum);
				t[right].pre = max(t[right].sum,0);
				t[right].suf = max(t[right].sum,0);
				t[right].value = t[p].set;
				t[right].set = t[p].set;
//				cerr<<"t["<<right<<"].value="<<t[p].set<<endl;	
			}
			t[p].set=114514;
		}/*else*/ if(t[p].reverse){
			if(left){
				t[left].reverse = !t[left].reverse;
				swap(t[left].left,t[left].right);
				swap(t[left].pre,t[left].suf);
			} 
			if(right){
				t[right].reverse = !t[right].reverse;
				swap(t[right].left,t[right].right);
				swap(t[right].pre,t[right].suf);
			}
			t[p].reverse = false;
		}
		t[0].clear();
	}
	
	void split(int p,int x,int &l,int &r){
		if(!p){
			l=r=0;
			return;
		}
		down(p);
		if(t[t[p].left].size+1<=x){
			l=p;
			split(t[l].right,x-t[t[p].left].size-1,t[l].right,r);
		}else{
			r=p;
			split(t[r].left,x,l,t[r].left);
		}up(p);
	}
	int merge(int l,int r){
		if(!l||!r)return l|r;
		if(t[l].rand<t[r].rand){
			down(l);
			t[l].right=merge(t[l].right,r);
			up(l);
			return l;
		}else{
			down(r);
			t[r].left=merge(l,t[r].left);
			up(r);
			return r;
		}
	}
	
	int create(int x){
		int p=mem.ask();
		t[p].left = t[p].right=0;
		t[p].rand = Rand();
		t[p].size = 1;
		t[p].value = t[p].sum = t[p].maxsum /*= t[p].pre = t[p].suf*/ = x;
		t[p].pre = t[p].suf = max(x,0); 
		t[p].set = 114514;
		t[p].reverse = false;
		return p; 
	}
	void insert(int x){
		root=merge(root,create(x)); 
	}
	void remove(int p){
		if(p==0)return;
		remove(t[p].left);
		mem.remove(p);
		remove(t[p].right);
	}
	void print(int p=root){
		if(!p)return;
		down(p);
		print(t[p].left);
		printf("%d ",t[p].value);
		print(t[p].right);
	}
	
	void insert(int x,int n){
		int pl;
		scanf("%d",&pl);
		int p=create(pl);
		for(int i=2;i<=n;i++){
			scanf("%d",&pl);
			p=merge(p,create(pl));
		}
		int l,r;
		split(root,x,l,r);
		root=merge(merge(l,p),r);
	}
	void remove(int x,int n){
		int l,r;
		split(root,x-1,l,root);
		split(root,n,root,r);
		remove(root);
		root=merge(l,r);
	}
	void reverse(int x,int n){
		int l,r;
		split(root,x-1,l,root);
		split(root,n,root,r);
		t[root].reverse = !t[root].reverse;
		swap(t[root].left,t[root].right);
		swap(t[root].pre,t[root].suf);
		root = merge(merge(l,root),r);
	} 
	void set(int x,int n,int tt){
		int l,r;
		split(root,x-1,l,root);
		split(root,n,root,r);
		t[root].sum = t[root].size * tt;
		t[root].maxsum = max(tt,t[root].sum);
		t[root].pre = max(tt,t[root].sum,0);
		t[root].suf = max(tt,t[root].sum,0);
		t[root].value = tt;
		t[root].set = tt;
		root = merge(merge(l,root),r);
	}
	int sum(int x,int n){
		int l,r;
		split(root,x-1,l,root);
		split(root,n,root,r);
//		up(root);
		int ans=t[root].sum;
		root = merge(merge(l,root),r);
		return ans; 
	}
	int maxsum(){
		return t[root].maxsum;
	}
	int maxsum(int x,int n){
		int l,r;
		split(root,x-1,l,root);
		split(root,n,root,r);
//		cerr<<"::";print(root);cerr<<endl;
//		cerr<<"root="<<root<<endl;
//		cerr<<"----------------------\nup("<<root<<") IS RUNNING!!!!!!\n";
//		up(root);
//		cerr<<"----------------------\nup("<<root<<") ISN'T RUNNING NOW.\n";
		int ans=t[root].maxsum;
//		cerr<<"maxsum("<<x<<","<<n<<"):root="<<root<<endl;
		root=merge(merge(l,root),r);
		return ans;
	}
}t;
main(){
//	freopen("test.in","r",stdin);
//	freopen("test.out","w",stdout);
	
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		int a;
		scanf("%d",&a);
		t.insert(a);
	}
	while(m--){
		string op;
		int x,n,tt;
		cin>>op;
		if(op=="INSERT"){
			scanf("%d %d",&x,&n);
			t.insert(x,n);
		}else if(op=="DELETE"){
			scanf("%d %d",&x,&n);
			t.remove(x,n);
		}else if(op=="REVERSE"){
			scanf("%d %d",&x,&n);
			t.reverse(x,n); 
		}else if(op=="MAKE-SAME"){
			scanf("%d %d %d",&x,&n,&tt);
			t.set(x,n,tt);
		}else if(op=="GET-SUM"){
			scanf("%d %d",&x,&n);
			printf("%d\n",t.sum(x,n));
		}else if(op=="GET"){
			scanf("%d",&x);
			printf("%d\n",t.sum(x,1));
		}else if(op=="MAX-SUM"){
			scanf("%d %d",&x,&n);
			printf("%d\n",t.maxsum(x,n));
//			t.print();
//			cerr<<endl; 
		}
	}
    
    /*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

P2042 [NOI2005]维护数列

更改 $N=5\times 10^5$,并将 .maxsum(x,n) 改为 .maxsum() 即可。