线段树

首先通过一个例题体会一下线段树可以解决什么样的问题。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;
}

(此处只是简单的讲解了线段树的单点更新,除此之外还应掌握区间更新)
参考资料:
百度百科
线段树入门
数据结构-线段树

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值