首先我们来看一下线段树的定义,线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。
线段树问题可以大致分为以下四类
1.单点更新:最简单最基础的线段树问题,思想是只更新叶子节点,然后回溯更新他的父亲节点.
2.区间更新:一段区间内的数据一起更改,需要用到延迟标记(刚开始会难理解,最好是跟着代码人肉走一遍),
3.区间合并:询问区间中满足条件的连续最长区间是的多少,回溯的时候需要对左右儿子的区间进行合并(hdu 1540)
4.扫描线问题:最典型的就是矩形面积并,周长并等题(poj 1151)
首先看单点更新问题,hdu 1166 :http://acm.split.hdu.edu.cn/showproblem.php?pid=1166
题目意思就是:给你n个数字,分别代表这n个点最初有的人数,随后可以对这n个点当中的一点进行增加或减少操作,并且还要查询某段区间的总和.线段树一般变化最大的就是询问,和更新,所以,大家只要把询问和更新给弄明白了,这题也就明白了,还有,线段树对递归要求有点高,递归不是很熟的同学,最好去把递归复习几遍,附上hdu1166的AC代码:
#include<stdio.h>
#include<string.h>
#define maxn 50000
int ans;
struct node
{
int left, right, sum;
int mid()//求中值
{
return (left + right) >> 1;
}
}tree[maxn * 4];
//建树[left,right],rt为节点序号
void btree(int left, int right, int rt)
{
tree[rt].left = left;
tree[rt].right = right;
if (left == right)
{
scanf("%d", &tree[rt].sum);
return;
}
int mid = tree[rt].mid();
btree(left, mid, rt << 1);
btree(mid + 1, right, rt << 1 | 1);
tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
}
//在[left,right]区间内查询[L,R]区间的总数
void query(int left, int right, int rt, int L, int R)
{
if (L <= left && right <= R)
{
ans += tree[rt].sum;
return;
}
int mid = tree[rt].mid();
if (R <= mid)//R在[left,mid]区间内
query(left, mid, rt << 1, L, R);
else if (L > mid)//L在[mid +1,right]区间内
query(mid + 1, right, rt << 1 | 1, L, R);
else
{
query(left, mid, rt << 1, L, R);
query(mid + 1, right, rt << 1 | 1, L, R);
}
}
//在[left,right]区间内,叶子节点为左右儿子都为pos的点的sum值加add
void update(int left, int right, int rt, int pos, int add)
{
if (left == right)
{
tree[rt].sum += add;
return;
}
int mid = tree[rt].mid();
if (pos <= mid)//pos值在[left,mid]区间内,则一直递归,直到叶子节点
update(left, mid, rt << 1, pos, add);
else
update(mid + 1, right, rt << 1 | 1, pos, add);
tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
}
int main()
{
int t, n, cnt;
int a, b;
char str[10];
cnt = 1;
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
btree(1, n, 1);
printf("Case %d:\n", cnt++);
while (scanf("%s", str))
{
if (str[0] == 'E')
break;
scanf("%d%d", &a, &b);
if (str[0] == 'Q')
{
ans = 0;
query(1, n, 1, a, b);
printf("%d\n", ans);
}
else if (str[0] == 'A')
update(1, n, 1, a, b);
else
update(1, n, 1, a, -b);
}
}
return 0;
}
//1
//10
//1 2 3 4 5 6 7 8 9 10
//Query 6 6
//Add 6 -3
//Query 6 6
第二个问题是区间更新的问题,这里涉及一个更新延迟的问题,那么问题来了,什么是更新延迟呢?为什么要用到更新延迟呢?要怎么使用更新延迟呢?
我们先解决第二个疑问,为了使所有sum值都保持正确,每一次插入操作可能要更新O(N)个sum值,从而使时间复杂度退化为O(N)。为了降低时间复杂度,我们引入了”更新延迟”.回到第一个问题,更新延迟的思想就是先在结点上做标记,而并非真正执行,直到根据查询操作的需要分成两部分时再执行更新操作。怎么使用详见代码.
现在,我们引入一个例题来感受一下更新延迟..poj3468
题目链接:http://poj.org/problem?id=3468
题目大意:给你n个数字,分别代表这n个点最初有的人数,随后可以对这n个点当中的某段区间进行增加操作,并且还要查询某段区间的总和.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 100000;
int t, n, q;
ll anssum;
struct node {
ll l, r;
ll addv, sum;
}tree[maxn << 2];
//由子节点递归回来,修改父节点中的信息
void maintain(int id)
{
if (tree[id].l >= tree[id].r)
return;
tree[id].sum = tree[id << 1].sum + tree[id << 1 | 1].sum;
}
//建树[l,r],id为节点序号
void build(int id, ll l, ll r)
{
tree[id].l = l;
tree[id].r = r;
tree[id].addv = 0;
tree[id].sum = 0;
if (l == r)
{
tree[id].sum = 0;
return;
}
ll mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
maintain(id);
}
//更新延迟:如果id节点有增量,更新其子节点的信息,如果id节点为叶子节点或无增量则不做任何操作
void pushdown(int id)
{
if (tree[id].l >= tree[id].r)
return;
if (tree[id].addv) {
ll tmp = tree[id].addv;
tree[id << 1].addv += tmp;
tree[id << 1 | 1].addv += tmp;
tree[id << 1].sum += (tree[id << 1].r - tree[id << 1].l + 1)*tmp;
tree[id << 1 | 1].sum += (tree[id << 1 | 1].r - tree[id << 1 | 1].l + 1)*tmp;
tree[id].addv = 0;//别忘了把这个节点恢复为初始化时候的状态(加过了以后就不能再加了)
}
}
//更新操作:从第id个节点开始,区间[l,r]内的数加val
void updateAdd(int id, ll l, ll r, ll val)
{
if (tree[id].l >= l && tree[id].r <= r)
{
tree[id].addv += val;
tree[id].sum += (tree[id].r - tree[id].l + 1)*val;
return;
}
pushdown(id);
ll mid = (tree[id].l + tree[id].r) >> 1;
//继续更新其子区间
if (l <= mid)
updateAdd(id << 1, l, r, val);
if (mid < r)
updateAdd(id << 1 | 1, l, r, val);
maintain(id);
}
//从第id个节点开始,询问[l,r]区间内所有值之和
void query(int id, ll l, ll r)
{
if (tree[id].l >= l && tree[id].r <= r) {
anssum += tree[id].sum;
return;
}
pushdown(id);
ll mid = (tree[id].l + tree[id].r) >> 1;
if (l <= mid)
query(id << 1, l, r);
if (mid < r)
query(id << 1 | 1, l, r);
maintain(id);
}
int main()
{
ll a[maxn];
scanf("%d%d", &n, &q);
build(1, 1, n);
for (int i = 1;i <= n;i++) {
scanf("%lld", &a[i]);
updateAdd(1, i, i, a[i]);
}
while (q--)
{
char qu;
ll a, b, c;
scanf(" %c",&qu);//%c前要有空格
if (qu == 'C') {
scanf("%lld%lld%lld", &a, &b, &c);
updateAdd(1, a, b, c);
}
else {
scanf("%lld%lld", &a, &b);
anssum = 0;
query(1, a, b);
printf("%lld\n", anssum);
}
}
return 0;
}
接下来是第三类问题,区间合并,先看一个模板题,hdu3911
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3911
题意:有白石和黑石两种石头
1.输入1,x,y,表示把[x, y]里面的黑改为白,白改为黑。
2.输入2,x,y,表示查询从左边起连续黑石的数量的最大值。
思路:用1标志黑石,0标志白石。
#include <iostream>
#include <cstdio>
#include <stack>
#include <algorithm>
using namespace std;
#define lrt rt << 1
#define rrt rt << 1 | 1
typedef struct Node {
int l, r, v;//v标记是否被覆盖
int lone, sone, rone;//lone表示从最左边数连续1的长度,rone表示从右边数连续1的长度,sone表示连续最长的1的个数
int lzero, szero, rzero;//lzero表示从左边数连续0的长度,rzero表示从右边数连续0的长度,szero表示连续最长的0的个数
}Node;
const int maxn = 500005;
int cmd;
int n, q;
int x[maxn];
Node tree[maxn << 2];
void pushUP(int rt, int len) {
tree[rt].lone = tree[lrt].lone; tree[rt].rone = tree[rrt].rone;
tree[rt].lzero = tree[lrt].lzero; tree[rt].rzero = tree[rrt].rzero;
if (tree[rt].lone == len - len / 2)
tree[rt].lone += tree[rrt].lone;
if (tree[rt].rone == len / 2)
tree[rt].rone += tree[lrt].rone;
if (tree[rt].lzero == len - len / 2)
tree[rt].lzero += tree[rrt].lzero;
if (tree[rt].rzero == len / 2)
tree[rt].rzero += tree[lrt].rzero;
tree[rt].sone = max(tree[lrt].sone, tree[rrt].sone);
tree[rt].sone = max(tree[rt].sone, tree[lrt].rone + tree[rrt].lone);
tree[rt].szero = max(tree[lrt].szero, tree[rrt].szero);
tree[rt].szero = max(tree[rt].szero, tree[lrt].rzero + tree[rrt].lzero);
}
void pushDOWN(int rt) {
if (tree[rt].v) {
tree[rt].v = 0;
tree[lrt].v = !tree[lrt].v;
tree[rrt].v = !tree[rrt].v;
swap(tree[lrt].lone, tree[lrt].lzero);
swap(tree[lrt].rone, tree[lrt].rzero);
swap(tree[lrt].sone, tree[lrt].szero);
swap(tree[rrt].lone, tree[rrt].lzero);
swap(tree[rrt].rone, tree[rrt].rzero);
swap(tree[rrt].sone, tree[rrt].szero);
}
}
void build(int l, int r, int rt) {
tree[rt].l = l;
tree[rt].r = r;
tree[rt].v = 0;
if (l == r) {
tree[rt].lone = tree[rt].rone = tree[rt].sone = (x[l] == 0) ? 0 : 1;
tree[rt].lzero = tree[rt].rzero = tree[rt].szero = (x[l] == 1) ? 0 : 1;
return;
}
int mid = (l + r) >> 1;
build(l, mid, lrt);
build(mid + 1, r, rrt);
pushUP(rt, r - l + 1);
}
void update(int L, int R, int rt) {
if (L <= tree[rt].l && tree[rt].r <= R) {
swap(tree[rt].lone, tree[rt].lzero);
swap(tree[rt].rone, tree[rt].rzero);
swap(tree[rt].sone, tree[rt].szero);
tree[rt].v = !tree[rt].v;
return;
}
pushDOWN(rt);
int mid = (tree[rt].l + tree[rt].r) >> 1;
if (L <= mid) update(L, R, lrt);
if (mid < R) update(L, R, rrt);
pushUP(rt, tree[rt].r - tree[rt].l + 1);
}
int query(int L, int R, int rt) {
if (L == tree[rt].l && R == tree[rt].r) return tree[rt].sone;
pushDOWN(rt);
int mid = (tree[rt].l + tree[rt].r) >> 1;
if (mid >= R) return query(L, R, lrt);
else if (mid + 1 <= L) return query(L, R, rrt);
else {
int tmp = max(query(L, mid, lrt), query(mid + 1, R, rrt));
tmp = max(tmp, min(tree[lrt].rone, mid - L + 1) + min(tree[rrt].lone, R - mid));
return tmp;
}
}
int main() {
//freopen("in", "r", stdin);
int a, b;
while (~scanf("%d", &n)) {
for (int i = 1; i <= n; i++) scanf("%d", &x[i]);
build(1, n, 1);
scanf("%d", &q);
while (q--) {
scanf("%d %d %d", &cmd, &a, &b);
if (cmd == 0) printf("%d\n", query(a, b, 1));
else update(a, b, 1);
}
}
return 0;
}