首先通过一个例题体会一下线段树可以解决什么样的问题。HDU1166.
题目的关键是下面这段话:
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
其中(1)、(2)都很好实现。但是(3)的暴力算法时间复杂度会达到n^2.明显的会超时。那么如何解决一个区间求和(最大值,最小值)的问题呢?那么就要用到线段树啦。
那么什么是线段树呢?
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
(树的每个节点代表某个区间,每个节点存储了这个区间的一些信息:和,最大值,最小值)
以求和为例。给定一个序列:1,3,5,7,9,11.求某个区间[l,r]的和。建立线段树如下。
例如:求[0,3]的和,则只需要求[0,2]+[3,3]的和即可。
求[0,5]的和,则只需要求[0,5]的和即可。
(注:节点的编号与端点下标的区别)
代码实现部分:
(1) 定义线段树。
struct node
{
int left; //区间左端点
int right; //区间右端点
int sum; //和
} Tree[MAXN << 2];
(2) 初始化线段树
//建立线段树
// 树的节点编号 左端点下标 右端点下标
void Build(int root, int start, int end)
{
//设置该节点所代表的区间的下标
Tree[root].left = start;
Tree[root].right = end;
//如果是根节点则设置单点值
if (start == end)
{
scanf("%d", &A[start]);
Tree[root].sum = A[start];
return;
}
//如果不是根节点,递归下一层节点
int mid = (start + end) >> 1;
Build(root << 1, start, mid);
Build((root << 1) + 1, mid + 1, end);
//维护sum值操作
maintain(root);
}
//更新节点值
void maintain(int root) //更新
{
int LC = root << 1;
int RC = (root << 1) + 1;
//该节点sum值=左儿子.sum+右儿子.sum
Tree[root].sum = Tree[LC].sum + Tree[RC].sum;
}
(3) 修改单点值
//更新
// 节点编号 待修改值得下标 欲修改的值
void update(int root, int pos, int value)
{
//若修改的值在这个节点的左右区间之间那么就直接更改此区间的sum值就可
if (Tree[root].left == Tree[root].right && Tree[root].left == pos)
{
Tree[root].sum += value;
return;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
//若更改节点的坐标位于此区间的左半部分
if (pos <= mid)
update(root << 1, pos, value);
else
update((root << 1) + 1, pos, value);
//更新维护sum值
maintain(root);
}
(4) 求和
//求和
// 节点的编号 查询区间左端点下标 查询区间右端点下标
int Query(int root, int start, int end)
{
// 若该节点的区间被待查询的区间所包含则返回sum值
if (start == Tree[root].left && Tree[root].right == end)
{
return Tree[root].sum;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
int ret = 0;
//待查询区间的右端点处于此节点所表示区间的左半部分
if (end <= mid)
ret += Query(root << 1, start, end);
else if (start >= mid + 1)
//待查询区间的右端点处于此节点所表示区间的左半部分
ret += Query((root << 1) + 1, start, end);
else
{
//横跨该区间的中间
ret += Query(root << 1, start, mid);
ret += Query((root << 1) + 1, mid + 1, end);
}
return ret;
}
HDU1166AC代码:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 1000005
#define INF 0x3fffffff
int A[MAXN];
//int max;
//int min;
struct node
{
int left;
int right;
int sum; //和
} Tree[MAXN << 2];
void maintain(int root)//更新
{
int LC = root << 1;
int RC = (root << 1) + 1;
Tree[root].sum = Tree[LC].sum + Tree[RC].sum;
}
void Build(int root, int start, int end)
//建立线段树
{
Tree[root].left = start;
Tree[root].right = end;
if (start == end)
{
scanf("%d", &A[start]);
Tree[root].sum = A[start];
return;
}
int mid = (start + end) >> 1;
Build(root << 1, start, mid);
Build((root << 1) + 1, mid + 1, end);
maintain(root);
}
void update(int root, int pos, int value)
//更新
{
if (Tree[root].left == Tree[root].right && Tree[root].left == pos)
{
Tree[root].sum += value;
return;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
if (pos <= mid)
update(root << 1, pos, value);
else
update((root << 1) + 1, pos, value);
maintain(root);
}
int Query(int root, int start, int end) //求和
{
if (start == Tree[root].left && Tree[root].right == end)
{
return Tree[root].sum;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
int ret = 0;
if (end <= mid)
ret += Query(root << 1, start, end);
else if (start >= mid + 1)
ret += Query((root << 1) + 1, start, end);
else
{
ret += Query(root << 1, start, mid);
ret += Query((root << 1) + 1, mid + 1, end);
}
return ret;
}
int main()
{
int t;
scanf("%d",&t);
int it=1;
while(it<=t)
{
printf("Case %d:\n",it++);
int n;
scanf("%d",&n);
Build(1, 1, n);
string q;
int aa, bb;
while(true)
{
cin>>q;
if(q=="End")
{
break;
}else if(q=="Query")
{
scanf("%d%d",&aa,&bb);
cout<<Query(1,aa,bb)<<endl;
}else if(q=="Add")
{
scanf("%d%d",&aa,&bb);
update(1,aa,bb);
}else
{
scanf("%d%d",&aa,&bb);
update(1,aa,-bb);
}
}
}
return 0;
}
再附一份简单易懂的线段树单点更新模板:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 1000005
#define INF 0x3fffffff
int A[MAXN];
//int max;
//int min;
struct node {
int left;
int right;
int max; //最大值
int sum; //和
int min; //最小值
} Tree[MAXN << 2];
void maintain(int root) //更新
{
int LC = root << 1;
int RC = (root << 1) + 1;
Tree[root].sum = Tree[LC].sum + Tree[RC].sum;
Tree[root].max = max(Tree[LC].max, Tree[RC].max);
Tree[root].min = min(Tree[LC].min, Tree[RC].min);
}
void Build(int root, int start, int end)
//建立线段树
{
Tree[root].left = start;
Tree[root].right = end;
if (start == end) {
scanf("%d", &A[start]);
Tree[root].sum = A[start];
Tree[root].max = A[start];
Tree[root].min = A[start];
return;
}
int mid = (start + end) >> 1;
Build(root << 1, start, mid);
Build((root << 1) + 1, mid + 1, end);
maintain(root);
}
void update(int root, int pos, int value)
//更新
{
if (Tree[root].left == Tree[root].right && Tree[root].left == pos) {
Tree[root].sum = value;
Tree[root].max = value;
Tree[root].min = value;
return;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
if (pos <= mid)
update(root << 1, pos, value);
else
update((root << 1) + 1, pos, value);
maintain(root);
}
int Query(int root, int start, int end) //求和
{
if (start == Tree[root].left && Tree[root].right == end) {
return Tree[root].sum;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
int ret = 0;
if (end <= mid)
ret += Query(root << 1, start, end);
else if (start >= mid + 1)
ret += Query((root << 1) + 1, start, end);
else {
ret += Query(root << 1, start, mid);
ret += Query((root << 1) + 1, mid + 1, end);
}
return ret;
}
int RminQ(int root, int start, int end)
//求最小值
{
if (start == Tree[root].left && Tree[root].right == end) {
return Tree[root].min;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
int ret = INF;
if (end <= mid)
ret = min(ret, RminQ(root << 1, start, end));
else if (start >= mid + 1)
ret = min(ret, RminQ((root << 1) + 1, start, end));
else {
int a = RminQ(root << 1, start, mid);
int b = RminQ((root << 1) + 1, mid + 1, end);
ret = min(a, b);
}
return ret;
}
int RmaxQ(int root, int start, int end)
//求最大值
{
if (start == Tree[root].left && Tree[root].right == end) {
return Tree[root].max;
}
int mid = (Tree[root].left + Tree[root].right) >> 1;
int ret = 0; //modify this
if (end <= mid)
ret = max(ret, RmaxQ(root << 1, start, end));
else if (start >= mid + 1)
ret = max(ret, RmaxQ((root << 1) + 1, start, end));
else {
int a = RmaxQ(root << 1, start, mid);
int b = RmaxQ((root << 1) + 1, mid + 1, end);
ret = max(a, b);
}
return ret;
}
int main() {
/*
*Build 1 1 n
*Update 1 pos value
*Query 1 l r
*RminQ 1 l r
*RmaxQ 1 l r
*/
int n, m;
while (scanf("%d%d", &n, &m) != EOF) {
Build(1, 1, n);
char q;
int aa, bb;
for (int i = 0; i < m; ++i) {
cin >> q >> aa >> bb;
if (q == 'Q') {
cout << RmaxQ(1, aa, bb) << endl;
} else {
update(1, aa, bb);
}
}
}
return 0;
}