线段树3种基础模型的理解和记忆(任意区间求和,任意区间的所有数加上相同数(懒操作),任意区间所有数变成同一个值再求和)

①首先是最普通的:区间求和并更新
int b[大小];//根据数据多少开多大的数组,储存每一个点上的值
 struct zxs{
	 int l,r,sum;
 }tree[大小*4];
 void build(int id,int l,int r){
	 tree[id].l=l;
	 tree[id].r=r;
	 if(l==r){
		tree[id].sum=b[l];//这是一个点,初始化和值就是这个点的值
		return;}
	 int mid=(r+l)/2;
build(id*2,l,mid);//每2分为子树,id就乘以2,代表子树的编号,id*2+1为右子树编号,id*2为左子树
build(id*2+1,mid+1,r);//注意,右子树区间是mid+1开始,不包括mid
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum; //这是一个递归过程,所以这个相加的过程实际上是从最末端开始,也就是从最小的区间(点)开始,一直往前面加加加,直到整个区间都更新了和值。
 }
 void update(int id,int pose,int x){
    int l=tree[id].l;
	int r=tree[id].r;
	 if(tree[id].l==pose&&tree[id].r==pose){
		 tree[id].sum+=x;
		 return;}  //找到了要更新的点,把要加的值给这个点的和值
	 int mid=(r+l)/2;
	 if(mid<pose)update(id*2+1,pose,x);//要找的位置在中点右边,往右子树找
	 else update(id*2,pose,x);//往左子树找,记住它的编号是id*2
	 tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;//与建树的递归过程一样,从后面一直加到前面
 }
int query(int id,int x,int y){//求x,y区间内的和
	  int l=tree[id].l;
	int r=tree[id].r;
	if(x<=l&&r<=y){return tree[id].su//等于也AC,不等于也AC,为什么呢?首先是从1,n开始对比,所以第一次比较如果满足该条件,那就说明x=1,y=n,否则只要在1,n内则会进行下一步,开始二分,二分之后如果还有x<=l&&r<=y,则说明一定是x<l&&y=r或x=l&&y>r,
此时返回tree[id].sum后还有一小部分是在mid左或者右(不可能左右都还有),这一小部分就已经在上一步分配个下一个子树处理了m;}
	int mid=(l+r)/2;
	if(y<=mid){return query(id*2,x,y);}
	else if(x>mid){return query(id*2+1,x,y);}
	else{//这个是当x,y区间在子树区间之内,也就是tree[id].l<l<mid<r<tree[id].r,所以直接把两个值相加
		return query(id*2,x,mid)+query(id*2+1,mid+1,y);}
	}

上面的查询过程需要注意的是:多了几个return,而且最后是返回数值的。



<pre name="code" class="cpp">②第二个线段树模型,懒处理操作,也就是可以更新一整个区间的和值(给一个区间,对于区间内的每个数加上一个相同的值)
然后查询任意区间内的和:
typedef long long ll;
 int N,M;
 ll b[101000];
 struct zxs{
	 int l,r;
	 ll sum,lazy;
 }tree[101000*4];
//建树过程与上面一致,不过多了一个初始化lazy=0,这个就是要给区间每个数都加的值
 void build(int id,int l,int r){
	 tree[id].l=l;
	 tree[id].r=r;
	 tree[id].lazy=0;
	 if(l==r){
		tree[id].sum=b[l];
		return;}
	 int mid=(r+l)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
 }
//push_down其实就是从父树更新到它的两个子树的操作
 void push_down(int id ){
	if(tree[id].lazy==0)return ;(预判,避免进行不必要的操作)
	 tree[id*2].sum+=tree[id].lazy*(tree[id*2].r-tree[id*2].l+1);//左子树整个区间的和值都更新
	 tree[id*2].lazy+=tree[id].lazy;//左子树lazy值更新
	 tree[id*2+1].sum+=tree[id].lazy*(tree[id*2+1].r-tree[id*2+1].l+1);
	 tree[id*2+1].lazy+=tree[id].lazy;
	 tree[id].lazy=0;//当父树给它的两个子树更新完后,说明不必再进行父树的push_down操作,所以lazy变为0,但子树lazy不为0,下一次push_down的就是子树,其实lazy有点像bool标记,需要操作时lazy不为0即true,不需要操作时为0即false,后面的模型里有些bool标记跟这个类似,现在先理解,后面就很容易理解了
 }

//注意题目给定的值,如果是%I64d、%lld的大数,一定要用long long
 void update(int id,int x,int y,ll n){
	 if(tree[id].l>=x&&tree[id].r<=y){
		 tree[id].lazy+=n;
                 tree[id].sum+=n*(tree[id].r-tree[id].l+1);//原来这样才是对的
		 //tree[id].sum+=n*(y-x+1);这个从理解上来说是错的,但把上面的改成这样也能AC,为什么?
		//经过一番思考,终于得到答案:如果一开始tree[id].l==x&&tree[id].r==y那么这样加是等效的,肯定没有错
		//如果一开始x,y的区间不满足上述条件,那么可以看见在update末尾是有一个
                 tree[id].sum=tree[id*2].sum+tree[id*2+1].sum,也就是最终还有一次彻底的正确的更新,
               因此也不会错,但是为了规范,一定得写成正确的tree[id].sum+=n*(tree[id].r-tree[id].l+1);
		 return;}
	 push_down(id);
	   int l=tree[id].l;
	int r=tree[id].r;
		 int mid=(r+l)/2;
	 if(mid<x) update(id*2+1,x,y,n);
	 else if(y<=mid) update(id*2,x,y,n);
	 else {update(id*2,x,mid,n);
	 update(id*2+1,mid+1,y,n);
	 }
	 tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;//看上面解释,这一步很重要
}
ll query(int id,int x,int y){
	if(x<=tree[id].l&&tree[id].r<=y){return tree[id].sum;}
	//刚开始写成x>=tree[id].l.......吐血。
	push_down(id);
	  int l=tree[id].l;
	int r=tree[id].r;
	int mid=(l+r)/2;
	if(y<=mid){return query(id*2,x,y);}
	else if(x>mid){return query(id*2+1,x,y);}
	else{
		return query(id*2,x,mid)+query(id*2+1,mid+1,y);//与第一个模型基本类似
	}	
}


 



③这个模型也是用来求区间和值的,但是每次更新的是给定区间的所有点的值,
所以计算和值的时候需要先判断该区间的和值是否相等,再乘以区间的大小,引入标记。
struct node
{
    int l,r;
    bool sta;
    int val;//没有sum,后面是直接return val*区间长度
    bool flag;
}tree[MAXN * 4];
int L;

void build(int id,int l,int r)
{
    tree[id].l = l;
    tree[id].r = r;
    tree[id].val = 1;
    tree[id].flag = false;//flag表示要不要对它进行操作,刚开始不用所以为false;
    tree[id].sta = true;//刚开始每个区间都相同,所以是true;
    if (l == r) return;//与之前l==r才进行操作不同,这里是要给所有的树标,所以当无法再分时return;
    int mid = (l + r) / 2;
    build(id * 2,l,mid);
    build(id * 2 + 1,mid + 1,r);
}

void push_down(int id)//只需要满足一个条件
{
    if (tree[id].flag)//需要对该树进行操作(更新)
    {
        tree[id * 2].val = tree[id * 2 + 1].val = tree[id].val;
        tree[id * 2].flag = true;//需要对子树继续操作(更新)
        tree[id * 2 + 1].flag = true;
        tree[id].flag = false;//父树已操作结束
        tree[id].sta = true;
        tree[id * 2].sta = true;
        tree[id * 2 + 1].sta = true;//既然要对id父树进行操作,那就说明该区间的值都要换成一个特定值,所以都相同,为true;
    }
}


void push_up(int id)//需要满足两个条件↓
{
    if (tree[id * 2].sta == true && tree[id * 2 + 1].sta == true)//当子树区间值都相同时,父树单值对应要更新为相同值;
    {
        if (tree[id * 2].val == tree[id * 2 + 1].val)//再次判断,防止刚开始的默认情况误更新
        {
            tree[id].val = tree[id * 2 + 1].val;
            tree[id].sta = true;//父区间也为相同true;
        }
        else tree[id].sta = false;//不满足条件false;
    }
    else tree[id].sta = false;
}

void update(int id,int l,int r,int val)//特别要注意的是需要先push_down再push_up
{
    if (tree[id].l >= l && tree[id].r <= r)//要操作的区间大于等于该树的区间
    {
        tree[id].flag = true;//要对该树进行操作
        tree[id].val = val;
        tree[id].sta = true;
        return;
    }
    push_down(id);//从父到子更新;
    int mid = (tree[id].l + tree[id].r) / 2;
    if (l > mid) update(id * 2 + 1,l,r,val);
    else if (r <= mid) update(id * 2,l,r,val);
    else
    {
        update(id * 2,l,mid,val);
        update(id * 2 + 1,mid + 1,r,val);
    }
    push_up(id);//从子到父再更新,确保无遗漏,或者说刚开始的push_down(id)只是对相邻的子树进行更新,然后一直dfs二分到最后,
                //所以最后一定要从末尾到前面再更新一次(有些父树没更新)。
}

int query(int id,int l,int r)
{
    if (tree[id].l >= l && tree[id].r <= r)  
    {
        if (tree[id].sta == true)
        {
           // printf("%d %d %d\n",tree[id].l,tree[id].r,tree[id].val);
            return tree[id].val * (tree[id].r - tree[id].l + 1);
        }
    }
    push_down(id); //看需不需要对id父树进行操作,也就是说可能有一些子树还没更新到,确保再二分时已经更新完毕。
    int mid = (tree[id].l + tree[id].r) / 2;
    if (l > mid) return query(id * 2 + 1,l,r);
    else if (r <= mid) return query(id * 2,l,r);
    else
    {
        return query(id * 2,l,mid) + query(id * 2 + 1,mid + 1,r);
    }
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值