0%

Segment Tree Beats专题练习

2016集训队论文集 吉如一《区间最值操作与历史最值问题》

1 hdu5306 Gorgeous Sequence

维护一个长度为$n$的序列,支持以下操作:
$0 l r t$:对于所有$i \in [l,r]$,将$a_i$修改为$\min \{ a_i,t\} $。
$1 l r$:查询区间$l,r$的最大元素的值。
$2 l r$:查询区间$l,r$元素的和。

多组数据,$\sum n, \sum m \le 10^6$。

做法

1)在线段树上维护区间内的最大值,最大值的个数,以及严格次大值。
2)递归到某个被修改区间完全包含的区间的时候,看这个区间的最大值、次大值和t的关系:

1.如果最大值小于等于t,那么直接返回。
2.如果严格次大值小于等于t,那么这次操作只会影响到区间内的最大值,打上标记之后返回。
3.否则,暴力递归左右子树。

复杂度

在论文中证明了是$O(m\log n)$。

代码

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
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define db long double
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1e6+10,M=N*4;
const int inf=(1ll<<31)-1;
int a[N],n;
int tag[M];
int mx1[M],mx0[M],num[M];
ll sum[M];
#define ls c<<1
#define rs c<<1|1
void upd0(int c,int x) { mx0[c]=max(mx0[c],x); }
void upd1(int c,int x,int y) {
if(x>mx1[c]) {
mx0[c]=mx1[c];
mx1[c]=x,num[c]=y;
}
else if(x==mx1[c]) num[c]+=y;
else upd0(c,x);
}
void push_up(int c) {
mx1[c]=mx1[ls],num[c]=num[ls],mx0[c]=mx0[ls];
upd1(c,mx1[rs],num[rs]);
upd0(c,mx0[rs]);
sum[c]=sum[ls]+sum[rs];
}
void Add_tag(int c,int t) {
sum[c]-=(mx1[c]-t)*(ll)num[c];
mx1[c]=t,tag[c]=t;
}
void push_down(int c) {
if(tag[c]<mx1[ls]) Add_tag(ls,tag[c]);
if(tag[c]<mx1[rs]) Add_tag(rs,tag[c]);
tag[c]=inf;
}
void build(int l,int r,int c) {
tag[c]=inf;
if(l==r) {
mx1[c]=sum[c]=a[l]; mx0[c]=-1; num[c]=1;
return;
}
int mid=l+r>>1;
build(l,mid,ls),build(mid+1,r,rs);
push_up(c);
}
int ql,qr,qt;
int qmx;
ll qsum;
void update(int l,int r,int c) {
if(ql<=l&&qr>=r) {
if(qt>=mx1[c]) return;
if(qt>=mx0[c]) return Add_tag(c,qt);
int mid=l+r>>1; push_down(c);
update(l,mid,ls),update(mid+1,r,rs);
push_up(c);
return;
}
int mid=l+r>>1; push_down(c);
if(ql<=mid) update(l,mid,ls);
if(qr>mid) update(mid+1,r,rs);
push_up(c);
}
void query(int l,int r,int c) {
if(ql<=l&&qr>=r) {
qmx=max(qmx,mx1[c]);
qsum+=sum[c];
return;
}
int mid=l+r>>1; push_down(c);
if(ql<=mid) query(l,mid,ls);
if(qr>mid) query(mid+1,r,rs);
push_up(c);
}
int main() {
int T; rd(T);
int q,op;
while(T--) {
rd(n),rd(q);
for(int i=1;i<=n;++i) rd(a[i]);
build(1,n,1);
while(q--) {
rd(op),rd(ql),rd(qr);
if(op==0) {
rd(qt);
update(1,n,1);
}
else {
qmx=-1,qsum=0;
query(1,n,1);
if(op==1) printf("%d\n",qmx);
else printf("%lld\n",qsum);
}
}
}
return 0;
}

2 Picks loves segment tree

维护一个长度为$n$的序列$A$,支持以下操作:
$1 l r x$:对于所有$i\in [l,r]$,$A_i + = x$。
$2 l r x$:对于所有$i\in [l,r]$,$A_i = \min \{ A_i, x\} $。
$3 l r$:查询区间$[l,r]$内所有元素的和。
$n,m\le 3\times 10^5$

做法

因为有区间加减标记之后区间的最大值、次大值仍然可以维护,所以直接沿用上一道题的做法。

复杂度

“可以证明时间复杂度的上界是$O(m\log ^2 n)$,尽管在实际运行过程中表现得更像$O(m\log n)$。”


3 AcrossTheSky loves segment tree

维护一个长度为$n$的序列$A$,支持以下操作:
$1 l r x$:对于所有的$i\in [l,r]$,$A_i = \min \{ A_i,x\} $。
$2 l r x$:对于所有的$i\in [l,r]$,$A_i = \max\{ A_i,x\} $。
$3 l r$:询问$\sum_{i=l}^r A_i$。

做法

维护区间最大值、严格次大值、最小值、严格次小值,然后套用前面的做法。

复杂度

“可以沿用上一道题的证明,复杂度上界为$O(m\log n)$。”


将区间最值操作转化成区间加减操作

从上面几道题的做法我们可以发现,我们可以将区间取最值操作转化成对区间最值的加减,分别用两种标记维护对区间内的最值、非最值的修改。这样,我们把区间最值操作转化成了区间加减操作,在此基础上我们将可以维护与区间最值有关的、更加复杂的操作。


4 bzoj3064 CPU监控

维护一个长度为$n$的序列$A$,支持以下操作:
$1 l r x$:将区间$[l,r]$的数加上$x$。
$2 l r x$:将区间$[l,r]$的数变成$x$。
$3 l r$:询问$\min \{ A_i \mid i\in [l,r]\}$。
$4 l r$:询问$\min \{ B_i \mid i\in [l,r]\}$。

初始的时候$B_i = A_i$。每一次操作过后,我们都让$B_i = \max \{ B_i,A_i \}$。

$n,m\le 10^5,x\in [-10^9,10^9]$

做法

1)

如果对一个区间赋值后又进行了区间加,可以看做对这个区间进行了两次赋值操作。故而,我们可以将对某个区间的操作分成两个阶段,第一个阶段是区间加,第二个区间是区间赋值,对两个阶段分别维护操作标记的最小值,这样就可以维护区间的历史最小值了。

2)

其实可以直接将操作归为这样的一个形式:给定参数$a,b$,对于要修改的元素$x$,修改后$x’ = \min \{ x+a,b\}$。

合并两个操作$(a_1,b_1),(a_2,b_2)$:$(a_1+a_2,\min \{ b_1+a_2,b_2\})$。

然后考虑维护区间历史最小值,也可以用一个形式类似的标记$(a,b)$,表示对于一个操作前区间最小值为$x$的区间,操作后区间最小值是$\min \{ x+a,b\}$。假设之前进行过的操作的标记是$(a_1,b_1)$,之前进行过的操作的历史最小标记是$(a_2,b_2)$,现在合并过来的操作的历史最小标记是$(a_3,b_3)$,那么合并后得到的二元组是$(\min \{ a_2 ,a_1+a_3\} ,\min \{ b_2,b_3,b_1+a_3 \}$。

这个做法可以使用于所有的有区间赋值、加、取最值、查询历史最值,并且取最值操作和查询的历史最值类型相同(都是max或者都是min)的题目。

注意代码实现的时候不要把$\infty $设置得过于大,因为合并两个操作的标记的时候会直接累加,必须保证累加过后不会爆long long。

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
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define db long double
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
#define ls c<<1
#define rs c<<1|1
const int N=100010,M=N*4;
const ll inf=1e12;
ll a[N]; int n;
ll mx[M],lmx[M];
struct item {
ll a,b;
item(ll a=0,ll b=-inf): a(a),b(b) {}
}tag1[M],tag2[M];
void push_up(int c) {
mx[c]=max(mx[ls],mx[rs]);
lmx[c]=max(lmx[ls],lmx[rs]);
}
void Add(int c,item t1,item t2) {
lmx[c]=max(lmx[c],max(t2.a+mx[c],t2.b));
mx[c]=max(t1.a+mx[c],t1.b);
tag2[c]=item(max(tag1[c].a+t2.a, tag2[c].a),max(tag1[c].b+t2.a,max(tag2[c].b,t2.b)));
tag1[c]=item(tag1[c].a+t1.a,max(tag1[c].b+t1.a,t1.b));

}
void push_down(int c) {
Add(ls,tag1[c],tag2[c]);
Add(rs,tag1[c],tag2[c]);
tag1[c]=tag2[c]=item(0,-inf);
}
void build(int l,int r,int c) {
if(l==r) {
mx[c]=lmx[c]=a[l];
return;
}
int mid=l+r>>1;
build(l,mid,ls),build(mid+1,r,rs);
push_up(c);
}
int ql,qr;
item qt1;
item qt2;
void update(int l,int r,int c) {
if(ql<=l&&qr>=r) {
Add(c,qt1,qt2);
// cout<<l<<' '<<r<<':'<<tag1[c].a<<' '<<tag1[c].b<<' '<<mx[c]<<' '<<lmx[c]<<endl;
return;
}
int mid=l+r>>1; push_down(c);
if(ql<=mid) update(l,mid,ls);
if(qr>mid) update(mid+1,r,rs);
push_up(c);
}
ll qans1,qans2;
void query(int l,int r,int c) {
if(ql<=l&&qr>=r) {
qans1=max(qans1,mx[c]);
qans2=max(qans2,lmx[c]);
return;
}
int mid=l+r>>1; push_down(c);
if(ql<=mid) query(l,mid,ls);
if(qr>mid) query(mid+1,r,rs);
push_up(c);
}
int main() {
rd(n);
for(int i=1;i<=n;++i) rd(a[i]);
build(1,n,1);
int q,l,r,t; char op[10];
rd(q);
while(q--) {
scanf("%s",op);
rd(ql),rd(qr);
if(op[0]=='P') {
rd(t);
qt1=item(t,-inf);
qt2=item(t,-inf);
update(1,n,1);
}
else if(op[0]=='C') {
rd(t);
qt1=item(-inf,t);
qt2=item(-inf,t);
update(1,n,1);
}
else {
qans1=qans2=-inf;
query(1,n,1);
if(op[0]=='Q') printf("%d\n",qans1);
else printf("%d\n",qans2);
}
}
return 0;
}

5 元旦老人与数列

维护一个长度为$n$的序列,支持以下操作:
$1 l r x$:对于所有$i\in [l,r]$,将$A_i$变成$A_i+x$。
$2 l r x$:对于所有$i\in [l,r]$,将$A_i$变成$\max \{ A_i,x\}$。
$3 l r$:查询区间$[l,r]$内元素的最小值。
$4 l r$,查询区间$[l,r]$内元素的历史最小值。

做法

沿用之前处理区间最值操作的那个思路,将操作分成对区间最小值的操作和对区间非最小值的操作。这样就变成了:对区间最小值加,对区间非最小值加,查询区间最小值,查询区间历史最小值。

可以分别对区间最小值和非最小值维护进行的操作,这些标记的和以及标记和的历史最大值。

注意在上传信息、下方标记的时候,儿子的区间最小值可能和父亲不一样。

下面这份代码中分别维护了对最小值和非最小值的标记,以及最小值和非最小值的历史最小值。实际上没有必要分别维护最小值和非最小值的历史最小值,因为我们在更新的时候也不会用到它们,直接记录整个区间的历史最小值就可以了。

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
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define db long double
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
#define ls c<<1
#define rs c<<1|1
const int N=5e5+10,M=N*4;
const ll inf=1e10;
int a[N],n;
ll hmi1[M],hmi0[M];
ll mi1[M],mi0[M]; // mi1 < mi0
ll tag0[M][2],tag1[M][2]; // 1->min 0->other elements
template <class T> inline void cmin(T &x,T y) { if(y<x) x=y; }
inline void upd0(int c,ll x) { mi0[c]=min(mi0[c],x); }
inline void upd1(int c,ll x) {
if(x<mi1[c]) mi0[c]=mi1[c],mi1[c]=x;
else if(x!=mi1[c]) upd0(c,x);
}
void push_up(int c) {
mi0[c]=mi0[ls],mi1[c]=mi1[ls];
upd1(c,mi1[rs]),upd0(c,mi0[rs]);
hmi1[c]=hmi0[c]=inf;
cmin((mi1[c]==mi1[ls]?hmi1[c]:hmi0[c]),hmi1[ls]);
cmin((mi1[c]==mi1[rs]?hmi1[c]:hmi0[c]),hmi1[rs]);
cmin(hmi0[c],min(hmi0[ls],hmi0[rs]));
}
inline void Add(int c,ll *t0,ll *t1,int flg) {
if(flg) {
hmi1[c]=min(hmi1[c],mi1[c]+t1[1]);
mi1[c]=mi1[c]+t1[0];
tag1[c][1]=min(tag1[c][1],tag1[c][0]+t1[1]);
tag1[c][0]+=t1[0];
}
else {
hmi1[c]=min(hmi1[c],mi1[c]+t0[1]);
mi1[c]=mi1[c]+t0[0];
tag1[c][1]=min(tag1[c][1],tag1[c][0]+t0[1]);
tag1[c][0]+=t0[0];
}
hmi0[c]=min(hmi0[c],mi0[c]+t0[1]);
mi0[c]=mi0[c]+t0[0];
tag0[c][1]=min(tag0[c][1],tag0[c][0]+t0[1]);
tag0[c][0]+=t0[0];
}
void push_down(int c) {
if(!(tag0[c][0]|tag0[c][1]|tag1[c][0]|tag1[c][1])) return;
int flg0=mi1[ls]<=mi1[rs],flg1=mi1[rs]<=mi1[ls];
Add(ls,tag0[c],tag1[c],flg0);
Add(rs,tag0[c],tag1[c],flg1);
tag0[c][0]=tag1[c][0]=0;
tag0[c][1]=tag1[c][1]=0;
}
void build(int l,int r,int c) {
tag0[c][0]=tag1[c][0]=0;
tag0[c][1]=tag1[c][1]=0;
if(l==r) {
mi1[c]=hmi1[c]=a[l];
mi0[c]=hmi0[c]=inf;
return;
}
int mid=l+r>>1;
build(l,mid,ls),build(mid+1,r,rs);
push_up(c);
}
int ql,qr;
ll qt;
ll t0[2],t1[2];
void update0(int l,int r,int c) {
if(ql<=l&&qr>=r) return Add(c,t0,t1,0);
int mid=l+r>>1; push_down(c);
if(ql<=mid) update0(l,mid,ls);
if(qr>mid) update0(mid+1,r,rs);
push_up(c);
}
void update1(int l,int r,int c) {
if(ql<=l&&qr>=r) {
if(mi1[c]>=qt) return;
if(mi0[c]>=qt) {
ll t0[]={0,0};
ll t1[]={qt-mi1[c],0};
return Add(c,t0,t1,1);
}
int mid=l+r>>1; push_down(c);
update1(l,mid,ls),update1(mid+1,r,rs);
push_up(c);
return;
}
int mid=l+r>>1; push_down(c);
if(ql<=mid) update1(l,mid,ls);
if(qr>mid) update1(mid+1,r,rs);
push_up(c);
}
ll ans0,ans1;
void query(int l,int r,int c) {
if(ql<=l&&qr>=r) {
ans0=min(ans0,mi1[c]);
ans1=min(ans1,min(hmi1[c],hmi0[c]));
return;
}
int mid=l+r>>1; push_down(c);
if(ql<=mid) query(l,mid,ls);
if(qr>mid) query(mid+1,r,rs);
push_up(c);
}
int main() {
int q,x,op; rd(n),rd(q);
for(int i=1;i<=n;++i) rd(a[i]);
build(1,n,1);
while(q--) {
rd(op),rd(ql),rd(qr);
if(op==1) {
rd(x);
t0[0]=x,t0[1]=min(0,x);
update0(1,n,1);
}
else if(op==2) {
rd(qt);
update1(1,n,1);
}
else {
ans1=inf,ans0=inf;
query(1,n,1);
if(op==3) printf("%lld\n",ans0);
else printf("%lld\n",ans1);
}
}
return 0;
}