什么是线段树
线段树,是一种 二叉搜索树 。它将一段区间划分为若干 单位区间 ,每一个节点都储存着一个区间。它功能强大 ,支持区间求和,区间最大值,区间修改,单点修改等操作。线段树的思想和分治思想很相像。线段树的每一个节点都储存着一段区间
[
L
.
.
.
R
]
[ L . . .R ]
[L...R] 的信息,其中叶子节点
L
=
R
L = R
L=R 。它的大致思想是:将一段大区间平均地划分成
2
2
2 个小区间,每一个小区间都再平均分成
2
2
2 个更小区间……以此类推,直到每一个区间的
L
L
L 等于
R
R
R (这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。
这样一来,每一次单点修改、单点查询的时间复杂度都只为
O
(
l
o
g
2
n
)
O ( log_2n )
O(log2n) 。但是,可以用线段树维护的问题必须满足区间加法 ,否则是不可能将大问题划分成子问题来解决的。
线段树的原理和实现
线段树主要是把一段大区间平均地划分成两段小区间进行维护,再用小区间的值来更新大区间。这样既能保证正确性,又能使时间保持在 l o g log log 级别(因为这棵线段树是平衡的)。也就是说,一个 [ L , R ] [ L , R ] [L,R] 的区间会被划分成 [ L , ( L + R ) / 2 ] [ L , (L + R)/ 2 ] [L,(L+R)/2] 和 [ ( L + R ) / 2 + 1 , R ] [ (L + R)/ 2 + 1 , R ] [(L+R)/2+1,R] 这两个小区间进行维护,直到 L = R L = R L=R 。
线段树一般需要四个函数 b u i l d ( r o o t , l , n ) build(root,l,n) build(root,l,n) 建树函数, m o d i f y ( r o o t , x , k ) modify(root,x,k) modify(root,x,k) 修改函数, q u e r y ( r o o t , l , r ) query(root,l,r) query(root,l,r) 查询函数, p u s h u p ( r o o t ) pushup(root) pushup(root) 更新函数。我们最常用的操作就是遍历,而树的遍历需要从根节点开始,所以每一个函数都需要传入根节点 r o o t root root 。
b u i l d ( r o o t , l , n ) build( root , l , n ) build(root,l,n) 建树函数
该函数是树的初始化,通过根节点,向下初始化所有子节点。
public static void build(int u, int l, int r) {
tree[u] = new node();
if(l == r){//叶子节点
tree[u].l = l;
tree[u].r = r;
}else {
tree[u].l = l;
tree[u].r = r;
int mid = (l + r)/2;
build(2*u, l, mid);
build(2*u+1, mid + 1, r);
}
}
m o d i f y ( r o o t , x , k ) modify( root , x , k ) modify(root,x,k) 修改函数
x x x 表示要修改的位置, k k k 表示修改的值,通过该函数对 x x x 处的值进行修改
public static void modify(int u, int x, long k) {
if(tree[u].l == tree[u].r) {
tree[u].v = k;
}else {
int mid = (tree[u].l + tree[u].r)/2;
if(x <= mid) {
modify(u*2, x, k);
}else {
modify(u*2+1, x, k);
}
pushup(u);//更新u
}
}
q u e r y ( r o o t , l , r ) query( root , l , r ) query(root,l,r) 查询函数
查询 l l l ~ r 区间的区间值
public static long qurey(int u, int l, int r) {
if(l <= tree[u].l && r >= tree[u].r) {
return tree[u].v;
}
long res = 0;
int mid = (tree[u].l + tree[u].r) / 2;
if(l <= mid ) {
res = qurey(u*2, l, r);
}
if(r > mid) {
res += qurey(u*2+1, l, r);
}
return res;
}
p u s h u p ( r o o t ) pushup( root ) pushup(root) 更新函数
在 m o d i f y modify modify 函数进行修改时,修改的是叶子节点,但叶子节点的修改也会影响父节点,通过该函数更新被修改的叶子节点的父节点的值
private static void pushup(int u) {
tree[u].v = tree[u*2].v + tree[u*2+1].v;
}
例题:小球与盒子
题目链接:小球与盒子 - 蓝桥云课 (lanqiao.cn)
思路:
线段树叶子节点存储的是某个盒子中小球的个数,非叶子节点存储的是一段区间内盒子中小球数量的和,通过 m o d i f y modify modify 实现某个盒子增加小球的数量的操作,通过 q u e r y query query 实现查询 l l l ~ r r r 个盒子的小球总量。在 m o d i f y modify modify 直接将叶子节点的值改为了 k k k ,那么在本题中,应该是叶子节点原来的值加上 k k k 。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class 小球与盒子 {
public static class node{
int l,r;
long v;
}
static node tree[];
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
String[] s = in.readLine().split(" ");
int n = Integer.parseInt(s[0]);
int m = Integer.parseInt(s[1]);
tree = new node[n*4];
build(1,1,n);
for (int i = 0; i < m; i++) {
s = in.readLine().split(" ");
int op = Integer.parseInt(s[0]);
int x = Integer.parseInt(s[1]);
int y = Integer.parseInt(s[2]);
if(op == 1) {
modify(1, x, y);
}else {
System.out.println(qurey(1, x, y));
}
}
}
private static void build(int u, int l, int r) {
tree[u] = new node();
if(l == r){
tree[u].l = l;
tree[u].r = r;
}else {
tree[u].l = l;
tree[u].r = r;
int mid = (l + r)/2;
build(2*u, l, mid);
build(2*u+1, mid + 1, r);
}
}
static long qurey(int u, int l, int r) {
if(l <= tree[u].l && r >= tree[u].r) {
return tree[u].v;
}
long res = 0;
int mid = (tree[u].l + tree[u].r) / 2;
if(l <= mid ) {
res = qurey(u*2, l, r);
}
if(r > mid) {
res += qurey(u*2+1, l, r);
}
return res;
}
static void modify(int u, int x, long k) {
if(tree[u].l == tree[u].r) {
tree[u].v += k;
}else {
int mid = (tree[u].l + tree[u].r)/2;
if(x <= mid) {
modify(u*2, x, k);
}else {
modify(u*2+1, x, k);
}
pushup(u);//更新u
}
}
static void pushup(int u) {
tree[u].v = tree[u*2].v + tree[u*2+1].v;
}
}