线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。 因为线段树的时间复杂度比较低,所以一旦看到题中出现求区间值 (和,最大/小值,乘积,取模,区间修改等等),我一般首选线段树,就算拿不到满分一般也可以得到七十分,如果加入优化的话甚至可以拿满分,比暴力好多了→ →。
1. 什么是线段树? 你可以将一列数看做一条线段,从中间开始不断分成两半,直到分到最终的每一个节点,比如从1~10的十个数,我们可以建立如下图的线段树
每一个区间【l,r】都代表了区间l到r的记录值,比如区间和啊,区间最小值之类的。
2.怎么建线段树? 建树函数build ( root , l , r ) 一开始root作为根节点1,然后我们将1记做root的左儿子l,n记做右儿子r,同时还要用lc / rc表示左 / 右儿子的节点。 mid=(l+r/)2,那么我们接下来要建的就是( lc , l ,mid ) 和( rc , mid+1 , 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
const int N=300001 ;
struct node{
int l,r,lc,rc;
long long sum,mark,ma;
}t[N];
void build (int x,int l,int r)
{
t[x].l=l; t[x].r=r;
if (l==r)
{
t[x].sum=a[l];
t[x].ma=a[l];
return ;
}
int mid=(l+r)/2 ;
t[x].lc=++tot; build(t[x].lc,l,mid);
t[x].rc=++tot; build(t[x].rc,mid+1 ,r);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
t[x].ma=max(t[t[x].lc].ma,t[t[x].rc].ma);
}
看懂了之后非常的简单易懂,对吧 (^o^)/ 【大雾 学会了建树,接下来的区间修改什么的就很容易理解了~
3.怎么区间修改? 区间修改是非常常见的,比如将 l ~ r 的所有值增加d之类的 那么首先我们很容易想到的是单点修改,就是和建树一样,一层一层搜到最底的点,然后更改它们的值,一层一层返回并在返回过程中修改区间值。 但是这样很麻烦并且时间复杂度会很高,所以我们机智地创造了lazy标记 这种神奇的东西
以将 l ~ r 的所有值增加d为例,假设当我们搜索到【l1,r1】时,发现l1~r1包括在了l~r的范围内,那么这时我们就可以只更改【l1,r1】区间的值,并且给这个区间加一个标记记录它的修改值d,表示我们只搜到了这个区间,然后我们就停下来,不用再向下修改了。因为对于这一次查询来说,我们只需要这个区间的值就够了。 那么问题在于,当下一次修改时,我们需要用到【l1,r1】一下的节点该怎么办? 我们只需要将【l1,r1】的lazy标记继续向下传递,并将它本身的标记归零,这样我们就可以继续向下修改了~ 好了,修改函数如下:
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
void push (int x)
{
if (t[x].mark==0 ) return ;
int M=t[x].mark,l=t[x].lc,r=t[x].rc;
t[l].mark+=M; t[l].sum+=M*(t[l].r-t[l].l+1 ); t[l].ma+=M;
t[r].mark+=M; t[r].sum+=M*(t[r].r-t[r].l+1 ); t[r].ma+=M;
t[x].mark=0 ;
}
void add (int x,int l,int r,int d)
{
if (l<=t[x].l&&r>=t[x].r)
{
t[x].mark+=d;
t[x].sum+=d*(t[x].r-t[x].l+1 );
t[x].ma+=d;
return ;
}
push(x);
int mid=(t[x].l+t[x].r)/2 ;
if (l<=mid) add(t[x].lc,l,r,d);
if (r>mid) add(t[x].rc,l,r,d);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
t[x].ma=max(t[t[x].lc].ma,t[t[x].rc].ma);
}
4.关于线段树的类型问题 无非就是和,最大/小值,乘积,取模,区间修改这些,我写了一个求区间和,区间修改,最大值的线段树模板,附上,如有错误请轻拍并告诉我= =
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
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std ;
const int N=300001 ;
int m,n,x,y,z,a[N],tot;
char w;
struct node{
int l,r,lc,rc;
long long sum,mark,ma;
}t[N];
void build (int x,int l,int r)
{
t[x].l=l; t[x].r=r;
if (l==r)
{
t[x].sum=a[l];
t[x].ma=a[l];
return ;
}
int mid=(l+r)/2 ;
t[x].lc=++tot; build(t[x].lc,l,mid);
t[x].rc=++tot; build(t[x].rc,mid+1 ,r);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
t[x].ma=max(t[t[x].lc].ma,t[t[x].rc].ma);
}
void push (int x)
{
if (t[x].mark==0 ) return ;
int M=t[x].mark,l=t[x].lc,r=t[x].rc;
t[l].mark+=M; t[l].sum+=M*(t[l].r-t[l].l+1 ); t[l].ma+=M;
t[r].mark+=M; t[r].sum+=M*(t[r].r-t[r].l+1 ); t[r].ma+=M;
t[x].mark=0 ;
}
int query (int x,int l,int r)
{
if (l<=t[x].l&&t[x].r<=r)
{
return t[x].sum;
}
push(x);
int ans=0 ;
int mid=(t[x].l+t[x].r)/2 ;
if (l<=mid) ans+=query(t[x].lc,l,r);
if (r>mid) ans+=query(t[x].rc,l,r);
return ans;
}
void add (int x,int l,int r,int d)
{
if (l<=t[x].l&&r>=t[x].r)
{
t[x].mark+=d;
t[x].sum+=d*(t[x].r-t[x].l+1 );
t[x].ma+=d;
return ;
}
push(x);
int mid=(t[x].l+t[x].r)/2 ;
if (l<=mid) add(t[x].lc,l,r,d);
if (r>mid) add(t[x].rc,l,r,d);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
t[x].ma=max(t[t[x].lc].ma,t[t[x].r].ma);
}
int compare (int x,int l,int r)
{
if (t[x].l>=l&&t[x].r<=r)
{
return t[x].ma;
}
push(x);
int mid=(t[x].l+t[x].r)/2 ,maxx=0 ;
if (l<=mid) maxx=max(maxx,compare(t[x].lc,l,r));
if (r>mid) maxx=max(maxx,compare(t[x].rc,l,r));
return maxx;
}
int main ()
{
scanf ("%d%d" ,&n,&m);
for (int i=1 ;i<=n;++i) scanf ("%d" ,&a[i]);
tot=1 ;
build(1 ,1 ,n);
for (int i=1 ;i<=m;++i)
{
cin >>w;
if (w=='s' )
{
scanf ("%d%d" ,&x,&y);
cout <<query(1 ,x,y)<<endl ;
}
if (w=='a' )
{
scanf ("%d%d%d" ,&x,&y,&z);
add(1 ,x,y,z);
}
if (w=='c' )
{
scanf ("%d%d" ,&x,&y);
cout <<compare(1 ,x,y)<<endl ;
}
}
return 0 ;
}
这道题可以当做线段树裸题来做,附上代码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
#include <iostream>
#include <cstdio>
using namespace std ;
const int N=2000001 ;
int n,m,a[N],tot;
struct node{
int l,r,lc,rc,mark;
long long sum;
}t[N];
void build (int x,int l,int r)
{
t[x].l=l; t[x].r=r;
if (l==r)
{
t[x].sum=a[l];
return ;
}
int mid=(l+r)/2 ;
t[x].lc=++tot; build(t[x].lc,l,mid);
t[x].rc=++tot; build(t[x].rc,mid+1 ,r);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
}
void push (int x)
{
if (t[x].mark==0 ) return ;
int l=t[x].lc,r=t[x].rc,ma=t[x].mark;
t[l].mark+=ma; t[l].sum=t[l].sum+ma*(t[l].r-t[l].l+1 );
t[r].mark+=ma; t[r].sum=t[r].sum+ma*(t[r].r-t[r].l+1 );
t[x].mark=0 ;
}
long long query (int x,int l,int r)
{
if (l<=t[x].l&&t[x].r<=r)
{
return t[x].sum;
}
push(x);
long long ans=0 ;
int mid=(t[x].l+t[x].r)/2 ;
if (l<=mid) ans=ans+query(t[x].lc,l,r);
if (r>mid) ans=ans+query(t[x].rc,l,r);
return ans;
}
void add (int x,int l,int r,int d)
{
if (l<=t[x].l&&t[x].r<=r)
{
t[x].mark+=d;
t[x].sum=t[x].sum+d*(t[x].r-t[x].l+1 );
return ;
}
push(x);
int mid=(t[x].l+t[x].r)/2 ;
if (l<=mid) add(t[x].lc,l,r,d);
if (r>mid) add(t[x].rc,l,r,d);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
}
int main ()
{
scanf ("%d%d" ,&n,&m);
for (int i=1 ;i<=n;++i)
scanf ("%d" ,&a[i]);
tot=1 ;
build(1 ,1 ,n);
int k,a1,b1,c;
for (int i=1 ;i<=m;++i)
{
scanf ("%d" ,&k);
if (k==1 )
{
scanf ("%d%d%d" ,&a1,&b1,&c);
add(1 ,a1,b1,c);
}
else
{
scanf ("%d%d" ,&a1,&b1);
printf ("%lld\n" ,query(1 ,a1,b1));
}
}
return 0 ;
}
6.【例题】小W 开关灯 【题目描述】 晚上到家小 W 通过开关灯来保持自己神经的兴奋以便清醒地理笔记。 N(2≤N≤100,000)盏灯被连续的编号为1..N。刚回到家的时候,所有的灯都是关闭的。 小 W 通过 N个按钮来控制灯的开关, 按第i个按钮可以改变第 i 盏灯的状态。 小 W 发出 M (1≤M≤100,000)条指令,每个指令都是两个整数中的一个(0 或 1)。 第 1 种指令(用 0 表示)包含两个数字S_i和 E_i(1≤S_i≤E_i≤N),它们表示起始开 关和终止开关。小 W需要把从 S_i 到 E_i 之间的按钮都按一次,就可以完成这个指令。 第2种指令(用 1 表示)同样包含两个数字S_i和E_i(1≤S_i≤E_i≤N),不过这种指令 是询问从 S_i到 E_i之间的灯有多少是亮着的。 请你帮助小 W 得到正确的答案。
【输入格式】 第 1 行: 用空格隔开的两个整数 N 和 M。 第 2..M+1 行: 每行表示一个操作, 有三个用空格分开的整数: 指令号, S_i 和 E_i。
【输出格式】 对于每一次询问, 输出一行表示询问的结果。
【输入输出样例】 note.in 4 5 0 1 2 0 2 4 1 2 3 0 2 4 1 1 4
note.out 1 2
基本的线段树的区间修改和区间查询操作。 记录区间开灯数,区间是否完全改变,并下传区间改变标记即可。 lazy标记不是很好改,容易写错,多写写完全理解就好了,注意它的传递方式。虽然线段树不是noip考试范围,而且不会考到lazy…………但是谁知道呢(╯‵□′)╯︵┻━┻
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
#include <iostream>
#include <cstdio>
using namespace std ;
const int N=2000000 ;
int n,m,tot;
struct node{
int l,r,lc,rc,sum,mark;
}t[N];
inline void build (int x,int l,int r)
{
t[x].l=l;t[x].r=r;
if (l==r)
{
t[x].sum=0 ;
return ;
}
int mid=(l+r)/2 ;
t[x].lc=++tot; build(t[x].lc,l,mid);
t[x].rc=++tot; build(t[x].rc,mid+1 ,r);
}
inline void lazy (int x)
{
int l=t[x].lc,r=t[x].rc,m=t[x].mark;
if (m%2 ==0 )
{
t[x].mark=0 ;
return ;
}
else
{
t[l].mark+=m; t[l].sum=(t[l].r-t[l].l+1 )-t[l].sum;
t[r].mark+=m; t[r].sum=(t[r].r-t[r].l+1 )-t[r].sum;
t[x].mark=0 ;
return ;
}
}
inline void change (int x,int l,int r)
{
if (l<=t[x].l&&t[x].r<=r)
{
t[x].mark++;
t[x].sum=(t[x].r-t[x].l+1 )-t[x].sum;
return ;
}
lazy(x);
int mid=(t[x].l+t[x].r)/2 ;
if (l<=mid) change(t[x].lc,l,r);
if (r>mid) change(t[x].rc,l,r);
t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum;
}
inline int query (int x,int l,int r)
{
if (l<=t[x].l&&t[x].r<=r)
{
return t[x].sum;
}
lazy(x);
int mid=(t[x].l+t[x].r)/2 ,ans=0 ;
if (l<=mid) ans+=query(t[x].lc,l,r);
if (r>mid) ans+=query(t[x].rc,l,r);
return ans;
}
int main ()
{
freopen("lites.in" ,"r" ,stdin );
freopen("lites.out" ,"w" ,stdout );
scanf ("%d%d" ,&n,&m);
tot=1 ;
build(1 ,1 ,n);
for (int i=1 ;i<=m;++i)
{
int h,s,e;
scanf ("%d%d%d" ,&h,&s,&e);
if (h==0 )
{
change(1 ,s,e);
}
else
{
printf ("%d\n" ,query(1 ,s,e));
}
}
return 0 ;
}