学习之前需要知道以下内容:
1. 递归
2. 二叉树
文章目录
- 线段树
- 介绍
- 用途
- 建树
- 修改
- 单点修改
- 区间修改
- 查询
- 代码实现。
- 建树
- 更新
- lazy传递
- 查询
- 优化一下
- 练习
- 洛谷 P3372 【模板】线段树 1
- 题目描述
- 题解
- 【模板】线段树 2
- 题目描述
- 题解
线段树
介绍
线段树是一种二叉树,也可以归类为二叉搜索树。可以将区间的修改、维护和查询的时间复杂度优化为log级别的。
构成
如一段1-6区间
可以分开为1-3和4-6然后继续放下拆分直到拆分为一个数的时候。
也就可以用树的标识。
用途
区间的加法
也可以结合 扫描线 用于二维面积并问题。
建树
存储方式我们使用数组来进行建树。这个要用到的就是二叉树的知识了。(对于 i i i 下标节点,左儿子下标为 2 ∗ i 2*i 2∗i,右儿子下标为 2 ∗ i + 1 2*i +1 2∗i+1 父节点下标为 i / / 2 i//2 i//2)
修改
单点修改
通过二叉树查询下标在进行修改。(二叉树查询为log级别)
然后更新大区间的值。
如修改下标2为数字3那么则需要更新为
区间修改
如将1-5的的值+2
则下面部分都需要+2
但是这样好像也没有啥优势,和数组一个个+2是一样的甚至,还多一些。
我们可以在这加一个缓存技术。
1-5就只需要更新这2个就可以了,然后在其lazy数组上设置为+2.
更新的值为长度*加的值
这里
10
10
10 就是
+
2
∗
3
+2*3
+2∗3 而
3
3
3 加上
2
∗
2
2*2
2∗2
每次当要用到子节点的时候就需要将lazy标记下存了。
如现在
[
1
−
3
]
[1-3]
[1−3]的标记为
2
2
2 当我们要用到
[
1
]
[1]
[1]的时候
[
3
]
[3]
[3]就+2
[
1
−
2
]
[1-2]
[1−2]也+2
而
[
1
−
3
]
[1-3]
[1−3] 设置为0
然后继续递归
查询
这里和二叉树查询一样
不过需要注意的是。lazy标记下存的操作,在这里也是需要的。
如查询
[
1
−
2
]
[1-2]
[1−2]的值。
首先在
[
1
−
6
]
[1-6]
[1−6]的左,然后发现在
[
1
−
3
]
[1-3]
[1−3]的左,但是lazy为+2
所以
l
z
a
y
[
1
−
3
]
=
0
,
l
z
a
y
[
1
−
2
]
=
2
,
l
a
z
y
[
3
]
=
2
lzay[1-3] = 0,lzay[1-2]=2,lazy[3]=2
lzay[1−3]=0,lzay[1−2]=2,lazy[3]=2.
所以
[
1
−
2
]
=
l
z
a
y
[
1
−
2
]
+
[
1
−
2
]
=
2
+
7
=
9
[1-2] = lzay[1-2]+[1-2] = 2+7=9
[1−2]=lzay[1−2]+[1−2]=2+7=9
代码实现。
建树
使用递归来实现
/**
* 建树
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @return 当前位置的值
*/
public long build(int l, int r, int pos) {
if (l == r) {
return tree[pos] = mapping[l - 1];
} else {
int mid = (l + r) >> 1;
return tree[pos] = build(l, mid, pos << 1)
+ build(mid + 1, r, pos << 1 | 1);
}
}
更新
每次从根节点开始进行。
如果查询和边界没有交点的时候停止递归
如果完全包括,那么就将这个区间更新。
更新规则:
- 区间值+value*长度
- 区间lazy +value
其他情况就是一部分在一部分不在,那么就进行向子节点查询
注意如果该节点lazy值不为空则需要向下传递。
向下查找完后,需要将子节点添加的值向本节点更新。
/**
* 更新
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @param value 更新值
* @return 当前位置的值
*/
public long getUpdate(int l, int r, int pos, int value) {
if (r < updateL || l > updateR)
return 0;
if (l >= updateL && r <= updateR) {
lazy[pos] += value;
long v = (long) value * (r - l + 1);
tree[pos] += v;
return v;
} else {
int mid = (l + r) >> 1;
updateLazy(l, r, pos, mid);
long ul = getUpdate(l, mid, pos << 1, value);
long ur = getUpdate(mid + 1, r, pos << 1 | 1, value);
tree[pos] += ul + ur;
return ul + ur;
}
}
lazy传递
更新规则如下,
- 子节点的lazy设置为本节点的lazy
- 子节点数值更新
- 本节点lazy设置为0
/**
* 更新懒标记
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @param mid 中间位置
*/
private void updateLazy(int l, int r, int pos, int mid) {
if (lazy[pos] != 0) {
lazy[pos << 1] += lazy[pos];
lazy[pos << 1 | 1] += lazy[pos];
tree[pos << 1] += lazy[pos] * (mid - l + 1);
tree[pos << 1 | 1] += lazy[pos] * (r - mid);
lazy[pos] = 0;
}
}
查询
这个没有太多好说的,和更新挺像的
一样需要注意的是lazy值的下移。
/**
* 查询
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @return 当前位置的值
*/
public long getN(int l, int r, int pos) {
if (l > getR || r < getL)
return 0;
if (l >= getL && r <= getR) {
return tree[pos];
} else {
int mid = (l + r) >> 1;
int i = pos << 1;
int j = i | 1;
updateLazy(l,r,pos,mid);
return getN(l, mid, i) +
getN(mid + 1, r, j);
}
}
优化一下
emm因为代码是我自己想的,考虑不是特别好。
看到别人的都是把上传单独抽出一个方法,我觉得还是抽出来比较简单明了一些。
public void pushUp(int x) {
tree[x] = (tree[x << 1] + tree[x << 1 | 1]) % p;
}
然后所有的更新都不需要返回值了
只需要在代码的最后加上一个就可以了
pushUp(pos);
如
public void getUpdate(int l, int r, int pos, long value) {
if (r < updateL || l > updateR) return;
if (l >= updateL && r <= updateR) {
tree[pos] = (tree[pos] + value * (r - l + 1)) % p;
add[pos] = (add[pos] + value) % p;
} else {
int mid = (l + r) >> 1;
updateLazy(l, r, pos, mid);
getUpdate(l, mid, pos << 1, value);
getUpdate(mid + 1, r, pos << 1 | 1, value);
pushUp(pos);
}
}
练习
洛谷 P3372 【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 k k k。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含 3 3 3 或 4 4 4 个整数,表示一个操作,具体如下:
1 x y k
:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k。2 x y
:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
样例 #1
样例输入 #1
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
样例输出 #1
11
8
20
提示
对于
30
%
30\%
30% 的数据:
n
≤
8
n \le 8
n≤8,
m
≤
10
m \le 10
m≤10。
对于
70
%
70\%
70% 的数据:
n
≤
10
3
n \le {10}^3
n≤103,
m
≤
10
4
m \le {10}^4
m≤104。
对于
100
%
100\%
100% 的数据:
1
≤
n
,
m
≤
10
5
1 \le n, m \le {10}^5
1≤n,m≤105。
保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} ≤1018。
【样例解释】
题解
import java.io.*;
public class Main {
static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static final PrintWriter print = new PrintWriter(System.out);
public static int nextInt() throws IOException {
st.nextToken();
return (int) st.nval;
}
public static void main(String[] args) throws IOException {
int n = nextInt();
int m = nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = nextInt();
}
SegmentTrees s = new SegmentTrees(a);
for (int i = 0; i < m; i++) {
int j = nextInt();
if (j == 1) {
s.update(nextInt(), nextInt(), nextInt());
} else
print.println(s.get(nextInt(), nextInt()));
}
print.flush();
}
}
class SegmentTrees {
private final long[] tree;//线段树
private final int size;
private final int[] mapping;
private final long[] lazy;
public SegmentTrees(int[] mapping) {
this.size = mapping.length;
this.mapping = mapping;
this.tree = new long[size * 4 + 3];
this.lazy = new long[size * 4 + 3];
build(1, size, 1);
}
/**
* 建树
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @return 当前位置的值
*/
public long build(int l, int r, int pos) {
if (l == r) {
return tree[pos] = mapping[l - 1];
} else {
int mid = (l + r) >> 1;
return tree[pos] = build(l, mid, pos << 1)
+ build(mid + 1, r, pos << 1 | 1);
}
}
public void pushUp(int x) {
tree[x] = (tree[x << 1] + tree[x << 1 | 1]);
}
public void update(int l, int r, int value) {
this.updateL = l;
this.updateR = r;
getUpdate(1, size, 1, value);
}
private int updateL;
private int updateR;
/**
* 更新
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @param value 更新值
* @return 当前位置的值
*/
public void getUpdate(int l, int r, int pos, int value) {
if (r < updateL || l > updateR) return;
if (l >= updateL && r <= updateR) {
lazy[pos] += value;
tree[pos] += (long) value * (r - l + 1);
} else {
int mid = (l + r) >> 1;
updateLazy(l, r, pos, mid);
getUpdate(l, mid, pos << 1, value);
getUpdate(mid + 1, r, pos << 1 | 1, value);
pushUp(pos);
}
}
/**
* 更新懒标记
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @param mid 中间位置
*/
private void updateLazy(int l, int r, int pos, int mid) {
if (lazy[pos] != 0) {
lazy[pos << 1] += lazy[pos];
lazy[pos << 1 | 1] += lazy[pos];
tree[pos << 1] += lazy[pos] * (mid - l + 1);
tree[pos << 1 | 1] += lazy[pos] * (r - mid);
lazy[pos] = 0;
}
}
public long get(int l, int r) {
this.getL = l;
this.getR = r;
return getN(1, size, 1);
}
private int getL;
private int getR;
/**
* 查询
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @return 当前位置的值
*/
public long getN(int l, int r, int pos) {
if (l > getR || r < getL)
return 0;
if (l >= getL && r <= getR) {
return tree[pos];
} else {
int mid = (l + r) >> 1;
int i = pos << 1;
int j = i | 1;
updateLazy(l, r, pos, mid);
return getN(l, mid, i) +
getN(mid + 1, r, j);
}
}
}
【模板】线段树 2
题目描述
如题,已知一个数列,你需要进行下面三种操作:
-
将某区间每一个数乘上 x x x
-
将某区间每一个数加上 x x x
-
求出某区间每一个数的和
输入格式
第一行包含三个整数 n , m , p n,m,p n,m,p,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含若干个整数,表示一个操作,具体如下:
操作
1
1
1: 格式:1 x y k
含义:将区间
[
x
,
y
]
[x,y]
[x,y] 内每个数乘上
k
k
k
操作
2
2
2: 格式:2 x y k
含义:将区间
[
x
,
y
]
[x,y]
[x,y] 内每个数加上
k
k
k
操作
3
3
3: 格式:3 x y
含义:输出区间
[
x
,
y
]
[x,y]
[x,y] 内每个数的和对
p
p
p 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 3 3 3 的结果。
样例 #1
样例输入 #1
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
样例输出 #1
17
2
提示
【数据范围】
对于
30
%
30\%
30% 的数据:
n
≤
8
n \le 8
n≤8,
m
≤
10
m \le 10
m≤10
对于
70
%
70\%
70% 的数据:$n \le 10^3
,
,
, m \le 10^4$
对于
100
%
100\%
100% 的数据:$ n \le 10^5
,
,
, m \le 10^5$
除样例外, p = 571373 p = 571373 p=571373
(数据已经过加强_)
样例说明:
故输出应为 17 17 17、 2 2 2( 40 m o d 38 = 2 40 \bmod 38 = 2 40mod38=2 )
题解
这个题目的难点较于普通的就在于对于乘法的控制了。
一个就是标记的下传。
如果同时有着乘法和加法标记应该怎么来做呢。
我们看到
(
x
+
d
)
∗
m
(x+d)*m
(x+d)∗m等价于
x
∗
m
+
d
∗
m
x*m+d*m
x∗m+d∗m,那么我们做向下传递的时候就可以传递
d
子
∗
m
+
d
父
d_子*m+d_父
d子∗m+d父
传递规则如下
- 对于区间和: t r e e [ p o s ] = t r e e [ p o s ] ∗ m u l + a d d ∗ l e n % p tree[pos] = tree[pos]*mul+add*len \%p tree[pos]=tree[pos]∗mul+add∗len%p
- 对于mul标记: m u l [ p o s ] = m u l [ p o s ] ∗ v a l u e % p mul[pos] = mul[pos]*value\%p mul[pos]=mul[pos]∗value%p
- 对于add标记: a d d [ p o s ] = a d d [ p o s ] + a d d [ p o s ] ∗ m u l [ p o s ] % p add[pos] = add[pos]+add[pos]*mul[pos] \%p add[pos]=add[pos]+add[pos]∗mul[pos]%p
import java.io.*;
import java.util.Arrays;
public class Main {
static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static final PrintWriter print = new PrintWriter(System.out);
public static int nextInt() throws IOException {
st.nextToken();
return (int) st.nval;
}
public static void main(String[] args) throws IOException {
int n = nextInt();
int m = nextInt();
int p = nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = nextInt();
}
SegmentTrees s = new SegmentTrees(a);
for (int i = 0; i < m; i++) {
int j = nextInt();
if (j == 1) {
s.update(nextInt(), nextInt(), -nextInt());
} else if (j == 2) {
s.update(nextInt(), nextInt(), nextInt());
} else {
print.println(s.get(nextInt(), nextInt()) % p);
}
}
print.flush();
}
}
class SegmentTrees {
private final long[] tree;//线段树
private final int size;
private final int[] mapping;
private final long[] add;
private final long[] mul;
private final int p = 571373;
public SegmentTrees(int[] mapping) {
this.size = mapping.length;
this.mapping = mapping;
this.tree = new long[size * 4 + 1];
this.add = new long[size * 4 + 1];
this.mul = new long[size * 4 + 1];
Arrays.fill(mul, 1);
build(1, size, 1);
}
/**
* 建树
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @return 当前位置的值
*/
public long build(int l, int r, int pos) {
if (l == r) {
return tree[pos] = mapping[l - 1] % p;
} else {
int mid = (l + r) >> 1;
return tree[pos] = (build(l, mid, pos << 1)
+ build(mid + 1, r, pos << 1 | 1)) % p;
}
}
private int updateL;
private int updateR;
public void pushUp(int x) {
tree[x] = (tree[x << 1] + tree[x << 1 | 1]) % p;
}
public void update(int l, int r, int value) {
this.updateL = l;
this.updateR = r;
if (value > 0)
getUpdate(1, size, 1, value);
else
getNewUpdate(1, size, 1, -value);
}
private void getNewUpdate(int l, int r, int pos, int value) {
if (r < updateL || l > updateR) return;
if (l >= updateL && r <= updateR) {
tree[pos] = (tree[pos] * value) % p;
mul[pos] = mul[pos] * value % p;
add[pos] = add[pos] * value % p;
} else {
int mid = (l + r) >> 1;
updateLazy(l, r, pos, mid);
getNewUpdate(l, mid, pos << 1, value);
getNewUpdate(mid + 1, r, pos << 1 | 1, value);
pushUp(pos);
}
}
/**
* 更新
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @param value 更新值
* @return 当前位置的值
*/
public void getUpdate(int l, int r, int pos, long value) {
if (r < updateL || l > updateR)
return;
if (l >= updateL && r <= updateR) {
tree[pos] = (tree[pos] + value * (r - l + 1)) % p;
add[pos] = (add[pos] + value) % p;
} else {
int mid = (l + r) >> 1;
updateLazy(l, r, pos, mid);
getUpdate(l, mid, pos << 1, value);
getUpdate(mid + 1, r, pos << 1 | 1, value);
pushUp(pos);
}
}
/**
* 更新懒标记
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @param mid 中间位置
*/
private void updateLazy(int l, int r, int pos, int mid) {
if (add[pos] != 0 || mul[pos] != 1) {
int ls = pos << 1;
int lr = ls | 1;
tree[ls] = (tree[ls] * mul[pos] % p + add[pos] * (mid - l + 1) % p) % p;
mul[ls] = mul[ls] * mul[pos] % p;
add[ls] = (add[ls] * mul[pos] % p + add[pos]) % p;
tree[lr] = (tree[lr] * mul[pos] % p + add[pos] * (r - mid) % p) % p;
mul[lr] = mul[lr] * mul[pos] % p;
add[lr] = (add[lr] * mul[pos] % p + add[pos]) % p;
add[pos] = 0;
mul[pos] = 1;
}
}
public long get(int l, int r) {
this.getL = l;
this.getR = r;
return getN(1, size, 1);
}
private int getL;
private int getR;
/**
* 查询
*
* @param l 左边界
* @param r 右边界
* @param pos 当前位置
* @return 当前位置的值
*/
public long getN(int l, int r, int pos) {
if (l > getR || r < getL)
return 0;
if (l >= getL && r <= getR) {
return tree[pos];
} else {
int mid = (l + r) >> 1;
int i = pos << 1;
int j = i | 1;
updateLazy(l, r, pos, mid);
return (getN(l, mid, i) +
getN(mid + 1, r, j)) % p;
}
}
}