线段树一:修改点的值求任意区间的值

强烈推荐:这篇文章------------------------(完全版)线段树

线段树的作用:解决区间计算问题。

例如:记录一个区间的最值(最大或最小值)和总量,并在区间的插入、删除、修改中维护这些最值和总量。

线段树是一颗二叉树。记为T(a,b),a,b表示顶点T为区间[a,b]。区间长度b-a记为L。递归定义T[a,b]:

若区间长度L>=1,区间[a,(a+b)/2]为T的左孩子,区间((a+b)/2,b]为T的右孩子

如图为区间[1,10]的线段树:

线段树的基本操作:

(1)建立线段树T(a,b)。

(2)将区间[c,d]插入到线段树T(a,b)。

(3)将区间[c,d]从线段树T(a,b)中删除。

(4)对线段树进行动态维护。

以下代码转载自:himdd的文章

线段树的建立:

int Pushup(int root) //把当前结点的信息更新到父节点 
{   sum[root]=sum[root<<1]+sum[root<<1|1];
}
void Build(int L,int R,int root)
{   if(L==R) 
    {   scanf("%d",&sum[root]);
        return ;
    }
    int mid=(L+R)>>1;
    Build(Lson); //左孩子 
    Build(Rson); //右孩子 
    Pushup(root); 
}

上面代码构造的树为,我没有画完整,但是已经能够体现问题了。

后面的数相当与这颗树的根,区间[1,10]的根设为1,所以[1,5]的就为1<<1=2,[6,10]的根为:(1<<1)+1=3,依次类推,然后用sum[root]表示根节点为root的所代表的这个区间的值,如:sum[1]保存的就是[1,10]这个区间的值。所以有:sum[1]=sum[2]+sum[3],即:sum[root]=sum[root>>1]+sum[(root>>1)+1]

线段树的修改:比方说我要修改某一个结点的值,那么我只需要沿着根结点1往下寻找,并记录这条路径,更新sum的值即可,比方说变化2:它需要修改的区间依次为:[1,10]

->[1,5]->[1,3]->[1,2]->[2,2]。具体操作见代码:

void Update(int q,int val,int L,int R,int root) //在根为root,区间为[L,R]中的线段树修改结点p的值增加val 
{   if(L==R)
    {   sum[root]+=val;
        return ;
    } 
    int mid=(L+R)>>1;
    if(q<=mid) Update(q,val,Lson); //说明p在左结点 
    else Update(q,val,Rson); //说明p在右结点 
    Pushup(root);
}

至于统计某一区间[a,b]的值,那么同样可以利用二分的思想:

比方说要求[4,8],二分mid=(4+8)/2=6,然后它就可以写成:[4,6]+[7,8]=[4,5]+[6,6]+[7,7]+[8,8]=[4,4]+[5,5]+...+[8,8]。代码见下题中的Query部分。

HDU 1166(敌兵布阵),相当于树状数组中的插点问线,初始化给你每个结点的值,然后更新每个点的值,求给定的任意一个区间的值。

#include<iostream>
#include<cstring>
#include<cstdio> 
using namespace std;
const int MAX=50010;
#define Lson L,mid,root<<1 //遇到Lson的时候强制替换为后面的语句 
#define Rson mid+1,R,root<<1|1
int n,sum[MAX<<2];
int Pushup(int root) //把当前结点的信息更新到父节点 
{   sum[root]=sum[root<<1]+sum[root<<1|1];
}
void Build(int L,int R,int root)
{   if(L==R) 
    {   scanf("%d",&sum[root]);
        return ;
    }
    int mid=(L+R)>>1;
    Build(Lson); //左孩子 
    Build(Rson); //右孩子 
    Pushup(root); 
}
void Update(int q,int val,int L,int R,int root) //在根为root,区间为[L,R]中的线段树修改结点p的值增加val 
{   if(L==R)
    {   sum[root]+=val;
        return ;
    } 
    int mid=(L+R)>>1;
    if(q<=mid) Update(q,val,Lson); //说明p在左结点 
    else Update(q,val,Rson); //说明p在右结点 
    Pushup(root);
}
int Query(int ql,int qr,int L,int R,int root) //在根为root,区间为[L,R]的线段树中,计算区间[ql,qr]的和  
{   if(ql<=L && R<=qr) return sum[root];
    int mid=(L+R)>>1; //利用二分的思想 
    int res=0;
    if(ql<=mid) res+=Query(ql,qr,Lson);
    if(qr>mid) res+=Query(ql,qr,Rson);
    return res;
}
int main()
{   int a,b,Case,num=1;
    scanf("%d",&Case);
    while(Case--)
    {   printf("Case %d:\n",num++);
        scanf("%d",&n);
        Build(1,n,1);   
        char op[10];
        while(scanf("%s",op))
        {   if(op[0]=='E') break;
            scanf("%d%d",&a,&b);
            if(op[0]=='A') Update(a,b,1,n,1);
            if(op[0]=='S') Update(a,-b,1,n,1);
            if(op[0]=='Q') printf("%d\n",Query(a,b,1,n,1));
        }
    } 
    return 0;
}

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值