算法学习-树状数组模板(JAVA实现)

本文最后更新于:January 19, 2022 pm

纸上得来终觉浅,绝知此事要躬行。路漫漫其修远兮,吾将上下而求索!知识是经过历史的巨人沉淀下来的,别总想着自己能够快速学会,多花点时间去看看,也许会发现些不同的东西。你能快速学会的、觉得简单的东西,对于别人来说也是一样的。人外有人,天外有天。当努力到达了一定的程度,幸运自会与你不期而遇。

目录

核心操作

求某一位数的二进制中最后一位1所在位置表示的数。

1
2
3
public static int lowbit(int x){
return x&(-x);
}

单点修改,单点查询

单点修改、单点查询用数组就可以实现。

单点修改,区间查询

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
public static int maxd = 100000+7;

public static int[] a = new int[maxd]; //原数组
public static int[] tree = new int[maxd]; //树状数组

public static int lowbit(int x){
return x&(-x);
}

//在x位置加上k,n为数组最大值
public static void update(int x,int k,int n){
while(x<=n){
tree[x]+=k;
x+=lowbit(x);
}
}

//区间查询求1~x的和(即前缀和)
public static int query(int x){
int ans = 0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}

区间修改,单点查询

区间修改借助差分是思想。代码同上,只是在调用的时候多调用一次。

1
2
3
//[x,y]区间内加上k
updata(x,k); //A[x] - A[x-1]增加k
updata(y+1,-k); //A[y+1] - A[y]减少k

区间修改,区间查询

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
import java.io.*;
import java.math.BigInteger;
import java.util.Scanner;


/**
* @Author DragonOne
* @Date 2021/12/5 21:27
* @墨水记忆 www.tothefor.com
*/
public class Main {
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
public static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
public static Scanner sc = new Scanner(System.in);

public static int maxd = 100000+7;

public static int[] a = new int[maxd]; //原数组
public static int[] sum1 = new int[maxd];
public static int[] sum2 = new int[maxd];

public static int lowbit(int x){
return x&(-x);
}

//在i位置加上k,n为数组最大值
public static void update(int i,int k,int n){
int x = i; //后面需要i,所以不能改变i的值
while(x<=n){
sum1[x] +=k;
sum2[x] +=k*(i-1);
x+=lowbit(x);
}
}

//求前缀和
public static int query(int i){
int ans = 0,x=i;
while(x>0){
ans+=i*sum1[x]-sum2[x];
x-=lowbit(x);
}
return ans;
}

public static void main(String[] args) throws Exception {

int n = nextInt();
for(int i=1;i<=n;++i){
a[i]=nextInt();
update(i,a[i]-a[i-1],n); //输入初值的时候,也相当于更新了值,差分中也是这样实现的
}
int x=nextInt();
int y = nextInt();
int k = nextInt();
//区间[x,y]加上k
update(x,k,n);
update(y+1,-k,n);
//求[x,y]区间和
int sum = query(y) - query(x-1);


closeAll();
}

public static void cinInit(){
cin.wordChars('a', 'z');
cin.wordChars('A', 'Z');
cin.wordChars(128 + 32, 255);
cin.whitespaceChars(0, ' ');
cin.commentChar('/');
cin.quoteChar('"');
cin.quoteChar('\'');
cin.parseNumbers(); //可单独使用来还原数字
}

public static int nextInt() throws Exception{
cin.nextToken();
return (int) cin.nval;
}
public static long nextLong() throws Exception{
cin.nextToken();
return (long) cin.nval;
}
public static double nextDouble() throws Exception{
cin.nextToken();
return cin.nval;
}
public static String nextString() throws Exception{
cin.nextToken();
return cin.sval;
}
public static void closeAll() throws Exception {
cout.close();
in.close();
out.close();
}

}

总结

其实个人感觉就只是两类,一类是区间修改、区间查询;然后其他的是一类。除了区间修改、区间查询的代码不同外,其他的都是一样的代码,只是在调用的灵活处理即可。

总的来说,个人感觉实现的思路都一样,都可以理解成单点修改和单点查询(前缀和)两大部分组成。