C++线段树初步(下)

来源:互联网 发布:购物群软件 编辑:程序博客网 时间:2024/06/04 18:52

  上期博客中我们谈到了基本线段树中的不足:区间整体操作效率低。那么,我们该如何优化线段树使其能够高效处理区间内的整体操作?
  废话不说,先上张模拟图。
  这里写图片描述
  (博主手绘…将就着看一下吧…)
  如上图,这是一棵完全的线段树,现在要使区间[2,5]的所有数据增加x。
  在进行修改操作的时候需要遍历到的节点在图中已用蓝色箭头标出。不难发现,当我们遍历到[3,4]时整个区间已被包含在内,只需要将改变的值存入该节点中,不需要再向下遍历到子节点了。事实上,若每个叶节点都遍历并修改,其时间复杂度还不如在数组中直接修改。
  具体实现方法:将线段树的结构体中增加一个变量seg,代表该父节点下所有子节点的变化量。当需要再次向下遍历、更新到子节点的时候将此遍历迁移到子节点上,同时使父节点上的此变量归零。

struct sd{    int left,right,seg,maxx,sum;    sd ()    {        memset(this,0,sizeof(this));    }  

博主事先把每个点的初存入了pre数组。

void pushup(int t){    tree[t].sum=tree[t*2].sum+tree[t*2+1].sum;    tree[t].maxx=max(tree[t*2].maxx,tree[t*2+1].maxx);    return;}void build_tree(int t,int lef,int rig){    tree[t].left=lef;tree[t].right=rig;    if(lef==rig)    {        tree[t].sum=pre[lef];        tree[t].maxx=pre[lef];        return;    }    int mid=(lef+rig)/2;    build_tree(t*2,lef,mid);    build_tree(t*2+1,mid+1,rig);    pushup(t);}

构造和更新操作和上期提到的简单线段树差不多。
接下来是至关重要的设置标记的环节。

void pushdown(int t,int lef,int rig){    int mid=(lef+rig)/2;    tree[t*2].seg+=tree[t].seg;//左子序列继承    tree[t*2+1].seg+=tree[t].seg;//右子序列继承    tree[t*2].maxx+=tree[t].seg;//更改最大值    tree[t*2+1].maxx+=tree[t].seg;    tree[t*2].sum+=(mid-lef+1)*tree[t].seg;//更改和    tree[t*2+1].sum+=(rig-mid)*tree[t].seg;    //(rig-(mid+1)+1)*tree[t].seg;    tree[t].seg=0;//清空父节点标记}

  这个步骤中值得注意的是左子序列和右子序列的边间不要混淆,以build_tree中的对应关系为准。
  然后是修改数据的操作:

void updata_add(int t,int lef,int rig,int lefbound,int rigbound,int val)//初次看时可能会有些晕,分别代表当前tree下标,左边界,右边界//查询的左右边界,更新的值{    if(lef>=lefbound&&rig<=rigbound)//若完全包含,则不再向下更新    {        tree[t].seg+=val;        tree[t].maxx+=val;        tree[t].sum+=(rig-lef+1)*val;//这个+1不能少        return;    }    if(tree[t].seg!=0)//向下扩展一层    {        pushdown(t,lef,rig);    }    int mid=(lef+rig)/2;    if(lefbound<=mid)//左右递归搜索更改    {        updata_add(t*2,lef,mid,lefbound,rigbound,val);    }    if(rigbound>mid)    {        updata_add(t*2+1,mid+1,rig,lefbound,rigbound,val);    }    pushup(t);//更新父节点}

接下来是两个查询函数,大同小异,放在一起讲解

int inquiry_sum(int t,int lef,int rig,int lefbound,int rigbound)//意义同上{    if(lefbound<=lef&&rigbound>=rig)    {        return tree[t].sum;//完全包含在区间查询范围中直接返回值。    }    if(tree[t].seg!=0)//向下扩展一层    {        pushdown(t,lef,rig);    }    int mid=(lef+rig)/2,sumnow=0;    if(lefbound<=mid)    sumnow+=inquiry_sum(t*2,lef,mid,lefbound,rigbound);    if(rigbound>=mid+1)    sumnow+=inquiry_sum(t*2+1,mid+1,rig,lefbound,rigbound);    return sumnow;} int inquiry_max(int t,int lef,int rig,int lefbound,int rigbound){    if(lefbound<=lef&&rigbound>=rig)//搜索左右子区间    {        return tree[t].maxx;    }    if(tree[t].seg!=0)    {        pushdown(t,lef,rig);    }    int mid=(lef+rig)/2,maxxx=0;    if(lefbound<=mid)    {        maxxx=max(maxxx,inquiry_max(t*2,lef,mid,lefbound,rigbound));    }    if(rigbound>=mid+1)    {        maxxx=max(maxxx,inquiry_max(t*2+1,mid+1,rig,lefbound,rigbound));    }    return maxxx;}

  总结:线段树区间操作优化其实就是延迟更新节点至必要时,来节约更改数值时每次不必要的更新子节点所花费的时间。但若要完全将一个区间内元素修改成某特定值,博主个人觉得还需一个seg2记录,更新时另写一个函数同样利用pushdown和pushup来操作,请读者自行思考解决。
  最后,贴上博主的辣鸡代码(写到了3000+)

#include<bits/stdc++.h>using namespace std;struct sd{    int left,right,seg,maxx,sum;    sd ()    {        memset(this,0,sizeof(this));    }};sd tree[500000];int pre[100000];void pushup(int t){    tree[t].sum=tree[t*2].sum+tree[t*2+1].sum;    tree[t].maxx=max(tree[t*2].maxx,tree[t*2+1].maxx);    return;}void build_tree(int t,int lef,int rig){    tree[t].left=lef;tree[t].right=rig;    if(lef==rig)    {        tree[t].sum=pre[lef];        tree[t].maxx=pre[lef];        return;    }    int mid=(lef+rig)/2;    build_tree(t*2,lef,mid);    build_tree(t*2+1,mid+1,rig);    pushup(t);}void pushdown(int t,int lef,int rig){    int mid=(lef+rig)/2;    tree[t*2].seg+=tree[t].seg;    tree[t*2+1].seg+=tree[t].seg;    tree[t*2].maxx+=tree[t].seg;    tree[t*2+1].maxx+=tree[t].seg;    tree[t*2].sum+=(mid-lef+1)*tree[t].seg;    tree[t*2+1].sum+=(rig-mid)*tree[t].seg;    tree[t].seg=0;}void updata_add(int t,int lef,int rig,int lefbound,int rigbound,int val){    if(lef>=lefbound&&rig<=rigbound)    {        tree[t].seg+=val;        tree[t].maxx+=val;        tree[t].sum+=(rig-lef+1)*val;        return;    }    if(tree[t].seg!=0)    {        pushdown(t,lef,rig);    }    int mid=(lef+rig)/2;    if(lefbound<=mid)    {        updata_add(t*2,lef,mid,lefbound,rigbound,val);    }    if(rigbound>mid)    {        updata_add(t*2+1,mid+1,rig,lefbound,rigbound,val);    }    pushup(t);}int inquiry_sum(int t,int lef,int rig,int lefbound,int rigbound){    if(lefbound<=lef&&rigbound>=rig)    {        return tree[t].sum;    }    if(tree[t].seg!=0)    {        pushdown(t,lef,rig);    }    int mid=(lef+rig)/2,sumnow=0;    if(lefbound<=mid)    sumnow+=inquiry_sum(t*2,lef,mid,lefbound,rigbound);    if(rigbound>=mid+1)    sumnow+=inquiry_sum(t*2+1,mid+1,rig,lefbound,rigbound);    return sumnow;} int inquiry_max(int t,int lef,int rig,int lefbound,int rigbound){    if(lefbound<=lef&&rigbound>=rig)    {        return tree[t].maxx;    }    if(tree[t].seg!=0)    {        pushdown(t,lef,rig);    }    int mid=(lef+rig)/2,maxxx=0;    if(lefbound<=mid)    {        maxxx=max(maxxx,inquiry_max(t*2,lef,mid,lefbound,rigbound));    }    if(rigbound>=mid+1)    {        maxxx=max(maxxx,inquiry_max(t*2+1,mid+1,rig,lefbound,rigbound));    }    return maxxx;}int main(){    int num,a,b,c,d;    printf("请输入数据个数(不超过1e6):\n");    scanf("%d",&num);    printf("请输入数据:");    for(int i=1;i<=num;i++)    {        scanf("%d",&pre[i]);    }    build_tree(1,1,num);    printf("\n");    printf("输入成功!开始进行操作\n操作指导:\n1,x,y-->查询x-y的和\n2,x,y-->查询x-y的最大值\n3,x,y,k-->将x-y数据加上k\n若要退出程序,请输入0\n");    while (true)    {        int k=1;        while(tree[k].sum !=0)    {        printf("%d ",tree[k].sum);        k++;    }    printf("\n");        scanf("%d",&a);        if(a==0)break;        if(a==1)        {            scanf("%d%d",&b,&c);            printf("第%d至第%d个数据的和是:%d\n",b,c,inquiry_sum(1,1,num,b,c));            continue;        }         if(a==2)         {            scanf("%d%d",&b,&c);            printf("第%d至第%d个数据的最大值是:%d\n",b,c,inquiry_max(1,1,num,b,c));            continue;        }        if(a==3)        {            scanf("%d%d%d",&b,&c,&d);            updata_add(1,1,num,b,c,d);            printf("操作成功!\n");            continue;        }    }    printf("感谢使用!");    return 0;}

以后的各档博客,我们将会继续讨论有关树的算法与数据结构,我们下一次不见不散,欢迎广大读者留言讨论。

原创粉丝点击