线段树
作用
1、单点修改
2、区间查询
相比于树状数组,线段树代码更复杂但应用更广泛
例题 Acwing 1264. 动态求连续区间和
给定 n n n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [ a , b ] [a,b] [a,b]的连续和。
输入格式
第一行包含两个整数 n n n和 m m m,分别表示数的个数和操作次数。
第二行包含 n n n 个整数,表示完整数列。
接下来 m m m 行,每行包含三个整数 k , a , b k,a,b k,a,b( k = 0 k=0 k=0,表示求子数列 [ a , b ] [a,b] [a,b]的和; k = 1 k=1 k=1,表示第 a a a 个数加 b b b )。
数列从 1 1 1 开始计数。
输出格式
输出若干行数字,表示 k = 0 k=0 k=0 时,对应的子数列 [ a , b ] [a,b] [a,b] 的连续和。
数据范围
1
≤
n
≤
100000
1≤n≤100000
1≤n≤100000,
1
≤
m
≤
100000
1≤m≤100000
1≤m≤100000,
1
≤
a
≤
b
≤
n
1≤a≤b≤n
1≤a≤b≤n,
数据保证在任何时候,数列中所有元素之和均在
i
n
t
int
int 范围内。
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35
Java代码
import java.util.*;
class Main
{
public static class Tr
{
int l;
int r;
int sum;
}
public static Tr[] tr = new Tr[400000]; //四倍N
public static int[] w = new int[100010];
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++)
{
w[i]=sc.nextInt();
}
build(1,1,n); //建立线段树
while(m-->0)
{
int k=sc.nextInt();
int a=sc.nextInt();
int b=sc.nextInt();
if(k==0) System.out.println(query(1,a,b));
else modify(1,a,b);
}
}
public static void pushup(int x)
{
tr[x].sum = tr[x<<1].sum+tr[x<<1|1].sum;
}
public static void build(int x,int l,int r)
{
tr[x]=new Tr();
tr[x].l=l;
tr[x].r=r;
if(l==r)
{
tr[x].sum=w[l];
}
else //这里else不能忘,否则报错 Index out of bounds for length
{
int mid=l+r>>1;
build(x<<1,l,mid); //递归左子树
build(x<<1|1,mid+1,r); //递归右子树
pushup(x);
}
}
public static int query(int x,int l,int r)
{
if(tr[x].l>=l && tr[x].r<=r) return tr[x].sum;
int mid=tr[x].l+tr[x].r>>1;
int sum=0;
if(l <= mid) sum+=query(x<<1,l,r); //遍历左子树
if(r > mid) sum+=query(x<<1|1,l,r); //遍历右子树
return sum;
}
public static void modify(int x,int u,int v)
{
if(tr[x].l == tr[x].r) tr[x].sum+=v;
else
{
int mid=tr[x].l+tr[x].r>>1;
if(tr[x].l<=u && u<=mid) modify(x<<1,u,v);
if(u>=mid+1 && u<=tr[x].r) modify(x<<1|1,u,v);
pushup(x);
}
}
}