本文首发自本人微信公众号:今天你A了吗。每日算法讲解,面试题,冲刺BAT大厂。微信扫码关注吧:
线段树:例题
1 前言
在上一章:《线段树:讲解与模版》中,我们讨论了线段树的原理并且给予了一套模版。我们在文中也提到,每个节点包含数组下标在 [i, j]范围内的聚合信息(如最大值,最小值,总和等)。具体细节大家可以点击上一篇文章进行查看。
这次,我们找到了一道题:杭电 1166:敌兵布阵(链接在本文末尾)。对线段树的应用进行讲解。本文中线段树维护范围中数组的和。
2 题目概述
给定一个一位数组代表各个军营将士的数量数量,给定三种方法:
- Add ai i 代表对数组中ai位置的元素加上i
- Sub ai i 代表对数组中ai位置的元素减去i
- Query i j 代表查询数组中下标i到j的和
本题目的核心操作是求数组的范围和,并且涉及到对数组元素进行改变。本题可以使用树状数组进行解题,效果更快,但是树状数组并不适合范围更新(只适合单点更新)。为了加深大家对上篇文章的理解,本文使用线段树进行解答。线段树有着更加广阔的适用范围。
3 解答
3.1 建树
(1)首先对数组进行输入, 并且开辟tree数组用于存储线段树
nums = new int[n+1]; //数组下标从1开始与tree数组下标相对应
tree = new int[n*4]; //4倍数足够大
(2)处理输入的字符串Add, Sub, Query, sub只需要把add的第二个参数变成负数即可。
public static void processIn(String in, int num1, int num2){
if(in.equals("Add")){
update(1, 1, n, num1, num2);
}else if(in.equals("Sub")){
update(1, 1, n, num1, -num2);
}else{
int res = query(1, 1, n, num1, num2);
System.out.println(res);
}
}
(3)与上篇文章一样,线段树的建树,更新,查询函数。注意:这里的更新不再是值的直接覆盖,而是把数组中的数加上和减去某个数。n是nums数组的长度。
public static void build(int root, int l, int r){
if(l == r){
tree[root] = nums[l];
return;
}
int mid = l + (r - l) / 2;
build(root * 2, l, mid);
build(root * 2 + 1, mid + 1, r);
tree[root] = tree[root * 2] + tree[root * 2 + 1];
}
public static void update(int root, int l, int r, int key, int val){
if(l == r){
if(l == key)
tree[root] += val;
return;
}
if(key < l || key > r) return;
int mid = l + (r - l) / 2;
update(root * 2, l, mid, key, val);
update(root * 2 + 1, mid + 1, r, key, val);
tree[root] = tree[root * 2] + tree[root * 2 + 1];
}
public static int query(int root, int l, int r, int ql, int qr){
if(l >= ql && r <= qr) return tree[root];
if(ql > r || qr < l) return 0;
int mid = l + (r - l) / 2;
int left_sum = query(root * 2, l, mid, ql, qr);
int right_sum = query(root * 2 + 1, mid + 1, r, ql, qr);
return left_sum + right_sum;
}
(4)代码汇总
package multiThreading;
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main{
public static int[] nums;
public static int[] tree;
public static int n;
public static void build(int root, int l, int r){
if(l == r){
tree[root] = nums[l];
return;
}
int mid = l + (r - l) / 2;
build(root * 2, l, mid);
build(root * 2 + 1, mid + 1, r);
tree[root] = tree[root * 2] + tree[root * 2 + 1];
}
public static void update(int root, int l, int r, int key, int val){
if(l == r){
if(l == key)
tree[root] += val;
return;
}
if(key < l || key > r) return;
int mid = l + (r - l) / 2;
update(root * 2, l, mid, key, val);
update(root * 2 + 1, mid + 1, r, key, val);
tree[root] = tree[root * 2] + tree[root * 2 + 1];
}
public static int query(int root, int l, int r, int ql, int qr){
if(l >= ql && r <= qr) return tree[root];
if(ql > r || qr < l) return 0;
int mid = l + (r - l) / 2;
int left_sum = query(root * 2, l, mid, ql, qr);
int right_sum = query(root * 2 + 1, mid + 1, r, ql, qr);
return left_sum + right_sum;
}
public static void processIn(String in, int num1, int num2){
if(in.equals("Add")){
update(1, 1, n, num1, num2);
}else if(in.equals("Sub")){
update(1, 1, n, num1, -num2);
}else{
int res = query(1, 1, n, num1, num2);
System.out.println(res);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int T = sc.nextInt();
for(int round = 1; round <= T; round++){
n = sc.nextInt();
nums = new int[n+1]; //数组下标从1开始与tree数组下标相对应
tree = new int[n*4]; //4倍数足够大
for(int i = 1; i <= n; i++){
nums[i] = sc.nextInt();
}
build(1, 1, n);
System.out.println("Case "+ round + ":");
while (true){
String s = sc.next();
if(s.equals("End"))
break;
int num1 = sc.nextInt();
int num2 = sc.nextInt();
processIn(s, num1, num2);
}
}
}
}
4 题目链接
HDU 1166 敌兵布阵: http://acm.hdu.edu.cn/showproblem.php?pid=1166
线段树(讲解与模版)链接:链接