线段树和树状数组,都是用来维护区间信息的,并且都有较高的效率来查询(都是logn级,但树状数组在效率上还是略快于线段树)或者修改信息。
线段树在建树的时候,要开4倍的空间。 树状数组只需要另开一个维护数组。
线段树的代码更繁琐,但它可以区间更新,而树状数组只能单点更新。
因此,当题目不需要区间更新的时候,树状数组优先,反之则只能用线段树。
通过HDU 1166这题可以体会线段树和树状数组的基础。当然这题这两种都能用的原因是不需要区间更新。
由于我用Java,一般的Scanner 超时,因此改用了BufferedReader来输入,代码也麻烦了点
树状数组代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
static int T,n,cnt;
static int [] sum = new int[50010];
static String [] op = new String[3];
static String [] num;
static void add(int k, int val){
while(k <= n){
sum[k] += val; //加上更新的值
k += k & -k; //找到它的上一层
}
}
static int sum(int k){
int s = 0;
while(k > 0){ //找到k的所有lowbit维护的区间,相加就是k维护的区间信息
s += sum[k];
k -= k & -k;
}
return s;
}
public static void main(String[] args) throws IOException {
T = Integer.parseInt(bf.readLine());
for(int t = 1; t <= T; t++){
System.out.println("Case "+t+":");
n = Integer.parseInt(bf.readLine());
Arrays.fill(sum, 0);
num = bf.readLine().split(" ");
for(int i = 1; i <= n; i++){//初始化
add(i,Integer.parseInt(num[i-1]));
}
while(true){
op = bf.readLine().split(" ");
if(op[0].equals("End")) break;
if(op[0].equals("Query")){
System.out.println(sum(Integer.parseInt(op[2]))-sum(Integer.parseInt(op[1])-1));
}
else if(op[0].equals("Add")){
add(Integer.parseInt(op[1]),Integer.parseInt(op[2]));
}
else{
add(Integer.parseInt(op[1]),-Integer.parseInt(op[2]));
}
}
}
}
}
线段树代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
static int T,n,cnt;
static int [] tree;
static String [] op = new String[3];
static String [] num;
static void init(int l, int r, int cur) throws IOException{
if(l == r){//叶子结点
tree[cur] = Integer.parseInt(num[cnt++]);
return ;
}
int m = (l + r) >> 1;//(l+r)/2
init(l, m, cur << 1);//init左子树
init(m+1, r, cur << 1 | 1);//init右子树
tree[cur] = tree[cur << 1] + tree[cur << 1 | 1];//该点和为左右子树的和
}
static int query(int rt, int l, int r, int i, int j){//lr 为当期得到的范围, ij为需要查的范围
if(l >= i && r <= j) return tree[rt];//范围内,直接return
int m = (l + r) / 2;//缩小范围
int ans = 0;
//查左右
if(i <= m) ans += query(rt << 1, l, m, i, j);//中间值大于m,说明还i还在外面,因此要继续查左子树
if(j > m) ans += query(rt << 1 | 1, m + 1, r, i, j);//同上
return ans;
}
static void update(int rt, int l, int r, int x, int val){
if(l == r) {tree[rt] += val; return;}
int m = (l + r) / 2;
if(x <= m) update(rt << 1, l, m, x, val);//x在左边
else update(rt << 1 | 1, m+1, r, x,val);//x在右边
tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];//更新x影响的父节点值
}
public static void main(String[] args) throws IOException {
T = Integer.parseInt(bf.readLine());
for(int t = 1; t <= T; t++){
cnt = 0;
System.out.println("Case "+t+":");
n = Integer.parseInt(bf.readLine());
num = new String[n];
num = bf.readLine().split(" ");
tree = new int[4*n+1];
init(1,n,1);
while(true){
op = bf.readLine().split(" ");
if(op[0].equals("End")) break;
int a = Integer.parseInt(op[1]), b = Integer.parseInt(op[2]);
if(op[0].equals("Query")){
System.out.println(query(1,1,n,a,b));
}
else if(op[0].equals("Add")){
update(1,1,n,a,b);
}
else{//sub
update(1,1,n,a,-b);
}
}
}
}
}