线段树的建立,单点修改,区间求和

线段树
一种用于区间处理的工具。当我们遇到求区间的最值,区间的和,区间修改数据的时候,如果有数组进行操作,复杂度有的时候可达到O(m*n),当数据量比较大的时候,就不可以采取这种方法了。
线段树是用一种大概类似于二叉树的方式所建立。二叉树在查找一个数据的时候,是二叉树折半查找的方式进行的,复杂度是O(log2^n),效率比较高。
最值问题
每个结点上的数字是这颗子树上的最小值。
建立
每个结点的值为他的左孩子和右孩子的和。当左右两边相等的时候,就是当前数组的值,然后结束递归,进行回溯。

void build_tree(int arr[],int tree[],int node,int start,int endd)//数组,树,当前结点,左端点,右端点
{
    if(start==endd){//如果左右端点相等,即树的值为数组目前的值
        tree[node]=arr[start];
    }
    else{
           int mid=(start+endd)/2;//中间值
           int left_node =2*node+1;//左孩子的结点值
           int right_node=2*node+2;//右孩子的结点值
           build_tree(arr,tree,left_node,start,mid);//左子树,左右端点折半
           build_tree(arr,tree,right_node,mid+1,endd);//右子树,左右端点折半
           tree[node]=tree[left_node]+tree[right_node];//结点的值为左右孩子的值相加
    }
}

点修改
修改一个数组中特定的点,要先搜索找到它的位置,然后修改,回溯的时候,将它所涉及的区间结点的值进行修改。

void update_tree(int arr[],int tree[],int node,int start,int endd,int idx,int val)//数组,树,当前结点,左端点,右端点,需要修改的下标,修改后的值
{
    if(start==endd){//如果左右端点相等,修改树当前结点的值,修改数组下标的值
        tree[node]=val;arr[idx]=val;
    }
    else{
        int mid=(start+endd)/2;
        int left_node=2*node+1;
        int right_node=2*node+2;
        if(idx>=start&&idx<=mid){//如果需要修改的下标在左孩子的区间中
            update_tree(arr,tree,left_node,start,mid,idx,val);
        }
        else{//如果需要修改的下标在右孩子的区间中
            update_tree(arr,tree,right_node,mid+1,endd,idx,val);
        }
        tree[node]=tree[left_node]+tree[right_node];//回溯时将它涉及的树结点修改,结点=左孩子的值+右孩子
        //cout<<node<<tree[node]<<endl;
    }
}

区间和
求一个区间的和的时候,要看他的区间的左右区间在哪边,然后进行查找,并返回值,最后将左右的值相加返回。

int query_tree(int arr[],int tree[],int node,int start,int endd,int L,int R)//数组,树,当前结点,左端点,右端点,区间的左端点,区间的右端点
{
	if(endd<L||start>R){//如果目前结点不在所要求和的区间内
	        return 0;//就返回0
	}
    else if(start==endd){//如果左右端点相等
        return tree[node];//就返回当前结点的值,即已经找到了那个对应的叶子结点
    }
    else if(start>=L&&endd<=R){//如果目前左右端点整个都在求和的区间内
        return tree[node];//那就可以直接返回这个结点的值了,即左右端点的和,就不用向下继续查找了
    }
    else{
        int mid=(start+endd)/2;
        int node_left=2*node+1;
        int node_right=2*node+2;
        int sum_left=query_tree(arr,tree,node_left,start,mid,L,R);//左孩子的值
        int sum_right=query_tree(arr,tree,node_right,mid+1,endd,L,R);//右孩子的值
        //cout<<sum_left<<" "<<sum_right<<endl;
        return sum_left+sum_right;//回溯时将值一步一步的向上回溯,最后将总值返回根节点
    }
}

完整的代码

#include<bits/stdc++.h>
using namespace std;
int arr[]={1,3,5,7,9,11};
int len=6;
int tree[110]={0};
void build_tree(int arr[],int tree[],int node,int start,int endd)
{
    if(start==endd){
        tree[node]=arr[start];
    }
    else{
           int mid=(start+endd)/2;
           int left_node =2*node+1;
           int right_node=2*node+2;
           build_tree(arr,tree,left_node,start,mid);
           build_tree(arr,tree,right_node,mid+1,endd);
           tree[node]=tree[left_node]+tree[right_node];
    }
}
void update_tree(int arr[],int tree[],int node,int start,int endd,int idx,int val)
{
    if(start==endd){
        tree[node]=val;arr[idx]=val;
    }
    else{
        int mid=(start+endd)/2;
        int left_node=2*node+1;
        int right_node=2*node+2;
        if(idx>=start&&idx<=mid){
            update_tree(arr,tree,left_node,start,mid,idx,val);
        }
        else{
            update_tree(arr,tree,right_node,mid+1,endd,idx,val);
        }
        tree[node]=tree[left_node]+tree[right_node];
        //cout<<node<<tree[node]<<endl;
    }
}
int query_tree(int arr[],int tree[],int node,int start,int endd,int L,int R)
{
    if(start==endd){
        return tree[node];
    }
    else if(endd<L||start>R){
        return 0;
    }
    else if(start>=L&&endd<=R){
        return tree[node];
    }
    else{
        int mid=(start+endd)/2;
        int node_left=2*node+1;
        int node_right=2*node+2;
        int sum_left=query_tree(arr,tree,node_left,start,mid,L,R);
        int sum_right=query_tree(arr,tree,node_right,mid+1,endd,L,R);
        cout<<sum_left<<" "<<sum_right<<endl;
        return sum_left+sum_right;
    }
}
int main ()
{
     ios::sync_with_stdio(false);
     build_tree(arr,tree,0,0,len-1);
     //for(int i=0;i<15;i++){
     //   cout<<tree[i]<<endl;
     //}
     //cout<<endl;
     update_tree(arr,tree,0,0,len-1,4,10);
     for(int i=0;i<15;i++){
        cout<<tree[i]<<endl;
     }
     cout<<endl;
     int ans=query_tree(arr,tree,0,0,len-1,2,5);
     cout<<ans<<endl;
     return 0;
}

这种比较麻烦,而且对于修改区间不太方便
另一种办法
进行“懒惰”标记,进行标记,等用到了这个区间,就按照这个标记进行修改

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e5+9;
typedef long long ll;
ll tree[3*N];
ll dis[3*N];
ll mp[N];
void build(ll l,ll r,ll node)//建树
{
    dis[node]=0;
    if(l==r){
        tree[node]=mp[l];return;
    }
    ll mid=(l+r)/2;
    build(l,mid,2*node);
    build(mid+1,r,2*node+1);
    tree[node]=tree[2*node]+tree[2*node+1];
}
void down(ll node,ll s)//这个标记被破坏了,就就将标记向下传递,自己本身的标记消失
{
    if(dis[node]){
        dis[node<<1]+=dis[node];//左孩子标记
        dis[node<<1|1]+=dis[node];//右孩子标记
        tree[node<<1]+=(s-(s>>1))*dis[node];//左结点值进行改变
        tree[node<<1|1]+=(s>>1)*dis[node];//右结点进行改变
        dis[node]=0;//标记消失
    }
}
void xiu(ll a,ll b,ll c,ll l,ll r,ll node)//区间修改
{
    if(l>=a&&r<=b){//在区间内,就整个区间进行修改
        tree[node]+=(r-l+1)*c;
        dis[node]+=c;
        return;
    }
    down(node,r-l+1);//被破坏了,更新标记
    ll mid=(l+r)/2;//分成两部分
    if(a<=mid)//小于mid的部分,就是左边子树部分
       xiu(a,b,c,l,mid,2*node)
    if(b>mid)
       xiu(a,b,c,mid+1,r,2*node+1);;//大于mid的部分,就是右边子树部分
    tree[node]=tree[2*node]+tree[2*node+1];//之后左右子树相加,进行tree数组的修改
}
ll qiu(ll a,ll b,ll l,ll r,ll node)//求和,与之前的操作相同
{
    if(l>=a&&r<=b) return tree[node];
    down(node,r-l+1);
    ll sum=0;
    ll mid=(l+r)/2;
    if(a<=mid)
        sum+=qiu(a,b,l,mid,2*node);
    if(b>mid)
        sum+=qiu(a,b,mid+1,r,2*node+1);
    return sum;
}
int main ()
{
    ll n,m;
    scanf("%lld %lld",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",&mp[i]);
    }
    build(1,n,1);
    while(m--)
    {
        getchar();
        char ch;scanf("%c",&ch);
        if(ch=='C'){
            ll a,b,c;scanf("%lld %lld %lld",&a,&b,&c);
            xiu(a,b,c,1,n,1);
        }
        else{
            ll a,b;scanf("%lld %lld",&a,&b);
            ll ans=qiu(a,b,1,n,1);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值