C++线段树初步(下)

  上期博客中我们谈到了基本线段树中的不足:区间整体操作效率低。那么,我们该如何优化线段树使其能够高效处理区间内的整体操作?
  废话不说,先上张模拟图。
  这里写图片描述
  (博主手绘…将就着看一下吧…)
  如上图,这是一棵完全的线段树,现在要使区间[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;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值