线段树入门(一)

接触线段树前我们先看一道比较经典的题目。

HDU 1166 敌兵布阵

这道题要求我们对一个区间内的单个值进行修改,并查询一段区间的和。
对于修改,我们很容易暴力的修改,复杂度为O(1),但查询的复杂度就变成了O(n),总复杂度为O(n*m),由于n和m都很大,所以这样肯定会超时的。
对于区间和我们常见的操作有前缀和,用前缀和可以将查询操作的复杂度降为O(1),但修改操作的复杂度则会升到O(n)。总复杂度也没有改变。
当这两种方法都无法满足题目的需求时,我们可以考虑是否可以将修改和查询的复杂度均摊一下,使总复杂度降低。
线段树解决这个问题比较好的工具。

什么是线段树

线段树

线段树是一种用树形来维护线性数据的一种数据结构。
一 般的线段树上的每一个节点T[a , b],代表该节点维护了原数列[ a , b ]区间的信息。对于每一个节点他至少有三个信息:左端点,右端点,我们需要维护的信息(在本题中我们维护区间和)。

struct segtree
{
    int l,r;//左右端点
    long long sum;//要维护的信息
}tree[maxn<<2];//一般开4*n防止爆空间

建立线段树

由于线段树是一个二叉树,而且是一个平衡二叉树,如果当前结点的编号是i,左端点为L ,右端点为 R , 那么左儿子的 编号为 i*2 ,左端点为 L ,右端点为 (L + R)/2 ; 同理右儿子的 编号为 i*2+1,左端点为(L+R)/2 ,右端点为 R)。如果当前结点的左端点等于右端点,那么该节点就是叶子节点,直接在该节点赋值即可。显然线段树是递归定义的。

void build(int root,int l,int r)
{
    tree[root].l = l;tree[root].r = r;//初始化左右端点
    if(l==r)//为叶子节点,直接赋值
        tree[root].sum = a[l];
    else
    {
        int mid = (l+r)>>1;
        build(root<<1,l,mid);//建立左子树
        build(root<<1|1,mid+1,r);//右子树
        push_up(root);//从下往上维护线段树
    }
}

从下往上更新线段树

void push_up(int x)
{
    tree[x].sum = tree[x<<1].sum + tree[x<<1|1].sum;//因题目不同而不同
}

单点更新

单点更新的方法很好理解,如果目标更新节点在左儿子里,去左儿子中查找;反之,在右儿子中。不断递归,知道找到需要维护的节点,更新它,然后从下往上维护线段树回来。这就是维护的过程,代码如下:

void update(int x,int q,long long val)
{
    int L=tree[x].l,R=tree[x].r;
    if(L==q&&R==q)//叶子节点,直接更新
        tree[x].num = val;
    int mid = (L+R)>>1;
    if(mid>=q)  update(x<<1,l,r,val);//在左子树中
    if(q>mid)   update(x<<1|1,l,r,val);//在右子树中
    push_up(x);//从下往上维护线段树
}

区间查询

题目中让我们查询区间求和,不难想到如果当前结点的区间完全被目标区间包含,直接返回当前结点的sum值,否则判断是否在左子树,右子树中,然后分别对左子树和右子树进行查询。具体过程通过以下代码理解:

long long query(int x,int l,int r)
{
    int L=tree[x].l,R=tree[x].r;
    if(l<=L&&R<=r)//完全在查询区间内
        return tree[x].sum;//直接返回值
    else
    {
        long long ans = 0;
        int mid = (L+R)>>1;
        if(mid>=l)  ans += query(x<<1,l,r);//在左子树中有一部分
        if(r>mid)   ans += query(x<<1|1,l,r);//在右子树中有一部分
        return ans;//返回答案
    }
}

这样我们就解决了这道题了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值