线段树

解决问题:

在数组区间中
更新某个数字(updata);
求数组某区间和(query);

解决方法一and 二

一. 直接用数组

方法:略
分析:

  • updata复杂度是O(1)
    -但 query复杂度O(n)

二. 前缀和

方法:
sum_a[i]=a[1]+...+a[i]
求解区间和时:a[j]+...a[i]=sum_a[i]-sum_a[j]
分析

  • query复杂度O(1)
  • 但updata复杂度O(n)

理解

每个叶子结点的值就是数组的值,
每个非叶子结点代表某区间和,
且左右两个孩子分别存储父亲一半的区间。
每个父亲的存储的值也就是两个孩子存储的值的和。
image
求和过程query
image

更改数据过程updata
image

建树

(不懂去看堆排序那章)

补成完全二叉树,用数组表示。
image
因此有性质:

image
(left代表左儿子,right代表右儿子)

代码


#include <bits/stdc++.h>
using namespace std;

#define MAX_LEN 1000

//node树的根节点
void build_tree(int arr[],int tree[],int node,int start,int end)
{
    if(start == end)
    {
        tree[node]=arr[start];
        return;
    }
    int mid = (start+end)/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,end);
    tree[node]=tree[left_node]+tree[right_node];



}


int main()
{
    int arr[] = {1,3,5,7,9,11};
    int size = 6;
    int tree[MAX_LEN] = {0};

    build_tree(arr,tree,0,0,size-1);
    int i;
    for(i=0;i<=14;i++)
    {
        cout<<"tree["<<i<<"]:"<<tree[i]<<endl;
    }
    return 0;
}


updata(更新数据)

void updata_tree(int arr[],int tree[],int node,int start,int end,int idx,int val)
{
    if(start == end)
    {
        arr[idx]=val;
        tree[node] = val;
        return;
    }


    int mid = (start + end) / 2;
    int left_node = 2*node +1;
    int right_node =2*node + 2;

    if(idx>start && idx<=mid)
    {
        updata_tree(arr,tree,left_node,start,mid,idx,val);
    }
    else
    {
        updata_tree(arr,tree,right_node,mid+1,end,idx,val);
    }
     tree[node]=tree[left_node]+tree[right_node];

}

query_tree 查询


int  query_tree(int arr[],int tree[],int node,int start,int end,int L,int R)
{
    if(R<start||L>end)//不在计算范围之内
    {
        return 0;
    }
    else
        if(start ==end)
    {
       return tree[node];
    }

    int mid  = (start+end)/2;
    int left_node = 2*node+1;
    int right_node = 2*node +2;
    int sum_left  =query_tree(arr,tree,left_node,start,mid,L,R);
    int sum_right =query_tree(arr,tree,right_node,mid+1,end,L,R);
    return sum_left+sum_right;
}

问题出现在:递归出口是 单个结点;实际上只需要startend在RL之间即可返回
优化:

int  query_tree(int arr[],int tree[],int node,int start,int end,int L,int R)
{
    if(R<start||L>end)//不在计算范围之内
    {
        return 0;
    }
    else
        if(start>=L&&end<=R)
    {
        return tree[node];
    } else
        if(start ==end)
    {
       return tree[node];
    }

    int mid  = (start+end)/2;
    int left_node = 2*node+1;
    int right_node = 2*node +2;
    int sum_left  =query_tree(arr,tree,left_node,start,mid,L,R);
    int sum_right =query_tree(arr,tree,right_node,mid+1,end,L,R);
    return sum_left+sum_right;
}

整体代码

#include <bits/stdc++.h>
using namespace std;

#define MAX_LEN 1000

//node树的根节点
void build_tree(int arr[],int tree[],int node,int start,int end)
{
    if(start == end)
    {
        tree[node]=arr[start];
        return;
    }
    int mid = (start+end)/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,end);
    tree[node]=tree[left_node]+tree[right_node];



}

void updata_tree(int arr[],int tree[],int node,int start,int end,int idx,int val)
{
    if(start == end)
    {
        arr[idx]=val;
        tree[node] = val;
        return;
    }


    int mid = (start + end) / 2;
    int left_node = 2*node +1;
    int right_node =2*node + 2;

    if(idx>start && idx<=mid)
    {
        updata_tree(arr,tree,left_node,start,mid,idx,val);
    }
    else
    {
        updata_tree(arr,tree,right_node,mid+1,end,idx,val);
    }
     tree[node]=tree[left_node]+tree[right_node];

}


int  query_tree(int arr[],int tree[],int node,int start,int end,int L,int R)
{
    if(R<start||L>end)//不在计算范围之内
    {
        return 0;
    }
    else
        if(start>=L&&end<=R)
    {
        return tree[node];
    } else
        if(start ==end)
    {
       return tree[node];
    }

    int mid  = (start+end)/2;
    int left_node = 2*node+1;
    int right_node = 2*node +2;
    int sum_left  =query_tree(arr,tree,left_node,start,mid,L,R);
    int sum_right =query_tree(arr,tree,right_node,mid+1,end,L,R);
    return sum_left+sum_right;
}

int main()
{
    int arr[] = {1,3,5,7,9,11};
    int size = 6;
    int tree[MAX_LEN] = {0};

    build_tree(arr,tree,0,0,size-1);
    int i;
    for(i=0;i<=14;i++)
    {
        cout<<"tree["<<i<<"]:"<<tree[i]<<endl;
    }

    updata_tree(arr,tree,0,0,size-1,4,6);
    cout<<endl;
    cout<<"up_data:"<<endl;
       for(i=0;i<=14;i++)
    {
        cout<<"tree["<<i<<"]:"<<tree[i]<<endl;
    }

    int s = query_tree(arr,tree,0,0,size-1,2,5);
    cout<<endl<<"query_tree: "<<s<<endl;
    return 0;
}

实战

模板题

C - 数组计算机
代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100007
long long int a[maxn];
long long int tree[4*maxn];

void build_tree(ll a[],ll tree[],int node,int start,int end)
{
    if(start==end)
    {
        tree[node]=a[start];
        return;
    }
    int mid = (start+end)/2;
    int left_node = 2*node+1;
    int right_node = 2*node+2;

    build_tree(a,tree,left_node,start,mid);
    build_tree(a,tree,right_node,mid+1,end);
    tree[node]=tree[left_node]+tree[right_node];
}


void updata(ll a[],ll tree[],int node,int start,int end,int p,int v)
{

    if(start==end)//此时头==尾==p+1但是不等于p
    {
        //cout<<start<<"**9"<<endl;
        tree[node]+=v;
        a[p]+=v;
        return;
    }
    int mid = (start+end)/2;
    int left_node = 2*node+1;
    int right_node = 2*node+2;
    if(p>start && p<=mid)
    {
        updata(a,tree,left_node,start,mid,p,v);
    }

    else //if(p>mid&&p<end)
    {
        updata(a,tree,right_node,mid+1,end,p,v);
    }

    tree[node]=tree[left_node]+tree[right_node];
}



ll query(ll a[],ll tree[],int node,int start,int end,int L,int R)
{
    if(R<start||L>end)//不在计算范围之内
    {
        return 0;
    }

    else if(start>=L&&end<=R)
    {
        return tree[node];
    }

    else if(start ==end)
    {
        return tree[node];
    }

    int mid=(start+end)/2;
    int  left_node = 2*node+1;
    int right_node = 2*node+2;
    ll sum_left= query(a,tree,left_node,start,mid,L,R);
    ll sum_right= query(a,tree,right_node,mid+1,end,L,R);
    return sum_left+sum_right;
}

int main()
{
    int n;

    while(scanf("%d",&n)!=EOF)
    {
        memset(tree,0,sizeof(tree));//清空树

        int i;
        for(i=1; i<=n; i++)
        {
            scanf("%lld",&a[i]);//数组
        }

        build_tree(a,tree,0,1,n);
        
        int q;
        scanf("%d",&q);
        long long int p,v;
        int L,R;
        while(q--)
        {
            int key;
            scanf("%d",&key);
            if(key==1)
            {
                scanf("%lld %lld",&p,&v);
                updata(a,tree,0,1,n,p,v);

            }
            else if(key==2)
            {
                scanf("%d%d",&L,&R);

                printf("%lld\n",query(a,tree,0,1,n,L,R));
            }
        }
    }
    return 0;
}


D - 效率至上
思路:
套用线段树模板,在线段树里加两个值,分别记录当前区间内的最小值和最大值,然后只需要每次选出区间内的最大值和最小值,将他们做减法输出即可。

posted @ 2021-08-24 08:36  kingwzun  阅读(44)  评论(0编辑  收藏  举报