线段树(敌兵布阵)

问题 http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1794;

 线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

       对于线段树中的每一个非叶子节点[a,b],当a != b时,将它分为两部分,它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b],当a == b时,该点为叶子节点,不再分。

由于线段树的运行量比较大,对于*和/运算,都用移位来代替,能减少时间

一个数左移1位,相当于该数乘以2,一个数右移1位,相当于除以2

定义一个结构体来储存

 

struct Node{

int left, right; //区间[left, right]

int sum;//区间里的数的和

}

Node node[150000];

int a[50000], SUM;

在这里节点的值即为结构体数组的下标

由上图可知,节点I的左儿子的节点为I * 2, 右儿子节点为 I * 2 + 1, I的父亲节点为I/2;

知道这些关系后就能建树


//对节点I进行建树

void buile(int left, int right, int i)

{

node[i].l = left;//I节点的左边界为left

node[i].r = right;//右边界为right

if(left == right)//当left == rigth时,说明已经是叶子节点,不再进行递归

{

node[i].sum = a[right];//当left == right时,为叶子节点,该区间([left, right])的和为a[left] 或a[right];

return ;//结束递归

}

build(left, (left + right) >> 1, i << 1); //当left != right 时,以区间[left, (left + right) / 2], 左儿子节点 i * 2 进行建树

build(((left + right) >> 1) + 1, right, (i << 1) + 1); //以区间[ (left + right) / 2 + 1, right], 右儿子节点 i * 2 + 1进行建树

node[i].sum = node[i << 1].sum + node[(i << 1)  + 1].sum;//I节点的sum值为左儿子节点的sum + 右儿子节点的sum

}


//查询函数, 查询区间[left, right]的sum值

void query(int left, int right, int i)

{

if(left <= node[i].l && right >= node[i].r)         //如果要查询的区间刚好覆盖节点I所对应的区间

SUM += node[i].sum;                                     //则区间[left, right]的区间和为结点I所对应的区间的sum值

else

{

int mi = (node[i].l + node[i].r) >> 1;

if(left > mi)                                                      //如果left > (left + right) / 2, 说明要查询的区间在[mi,right]里,顺着结点I的右儿子进行查询

{                                                                       

query(left, right, (i << 1) + 1);

}

else if(right <= mi)//如果right < (left + right) / 2, 则顺着结点I的左儿子进行查询

{

query(left, right, i << 1);

}

else

{//如果不是以上的情况,则说明所查询的区间被断开

query(left, right, i << 1);//顺着左儿子结点进行查询

query(left, right, (i << 1) + 1);//顺着右儿子结点进行查询

}

}

}


//点更改

void add(int x, int y, int i)

{

node[i].sum += y;//先把I区间的sum加上y;

if(node[i].left == x && node[i].right == x)//当递归到叶子节点时,结束递归

return ;

int mi = (node[i].l + node[i].r) >> 1;

if(x > mi)                                                         //如果x > mi,说明[mi, right]也增加了,因此把右儿子结点更新

{

add(x, y, (i << 1) + 1);

}

else

{

add(x, y, i << 1);柄                                        //否则更新左儿子结点

}

}


//同样,把y点改为x


void sub(int x, int y, int i)

{

node[i].sum -= y;

if(node[i].left == x && node[i].right == x)

return ;

int mi = (node[i].l + node[i].r) >> 1;

if(x > mi)

{

sub(x, y, (i << 1) + 1);

}

else

{

sub(x, y, i << 1);

}

}


完整代码:


#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;


struct Node{
    int l, r;
    int sum;
};


Node node[200000];
int a[50005], SUM;


void build(int l, int r, int i)
{
    node[i].l = l;
    node[i].r = r;
    if(l == r)
    {
        node[i].sum = a[r];
        return ;
    }
    build(l, (l + r) >> 1, i << 1);
    build(((l + r) >> 1) + 1, r, (i << 1) + 1);
    node[i].sum = node[i << 1].sum + node[(i << 1) + 1].sum;
}


void query(int l, int r, int i)
{
    if(l <= node[i].l && r >= node[i].r)
    {
        SUM += node[i].sum;
    }
    else
    {
        int mi = (node[i].l + node[i].r) >> 1;
        if(l > mi)
        {
            query(l, r, (i << 1) + 1);
        }
        else if(r <= mi)
        {
            query(l, r, i << 1);
        }
        else
        {
            query(l, r, i << 1);
            query(l, r, (i << 1) + 1);
        }
    }
}


void add(int x, int y, int i)
{
    node[i].sum += y;
    if(node[i].l == x && node[i].r == x)
        return ;
    int mi = (node[i].l + node[i].r) >> 1;
    if(x > mi)
    {
        add(x, y, (i << 1) + 1);
    }
    else
    {
        add(x, y, i << 1);
    }
}


void sub(int x, int y, int i)
{
    node[i].sum -= y;
    if(node[i].l == x && node[i].r == x)
        return ;
    int mi = (node[i].l + node[i].r) >> 1;
    if(x > mi)
    {
        sub(x, y, (i << 1) + 1);
    }
    else
    {
        sub(x, y, i << 1);
    }
}


int main(void)
{
    int t;
    scanf("%d", &t);
    int Case = 1;
    while(t--)
    {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) //注意这里是从1到n
            scanf("%d", &a[i]);
        build(1, n, 1); ////以区间[1, n]建立线段树, 结点为1
        char cmd[10];
        printf("Case %d:\n", Case++);
        while(scanf("%s", cmd) != EOF && strcmp("End", cmd) != 0)
        {
            if(strcmp("Query", cmd) == 0)
            {
                SUM = 0;
                int l, r;
                scanf("%d %d", &l, &r);
                query(l, r, 1); //从第一个结点开始查询
                printf("%d\n", SUM);
            }
            else if(strcmp("Add", cmd) == 0)
            {
                int x, y;
                scanf("%d %d", &x, &y);
                add(x, y, 1); //从第一个结点开始增加
            }
            else
            {
                int x, y;
                scanf("%d %d", &x, &y);
                sub(x, y, 1);
            }
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值