线段树-模板

树状数组and线段树

感悟:

1.了解线段树
2.区间修改,查询 以及建树

来一道题目理解理解:洛谷题目链接
线段树视频讲解配有代码详解
题意:题意很简单,给一a数组,对a数组有两种操作1,和2,输出操作2的结果.

1 x y k:将区间 [x, y][x,y] 内每个数加上 k。
2 x y:输出区间 [x, y][x,y] 内每个数的和。

思路: 线段树的区间修改和区间查询。

简单介绍一下线段树:
用一tree数组存放每个a数组一个区间和的值。 下面拿一张图直观了解下线段树的大致模样。
拿个图来理解理解
tree数组(二叉树构造)和a数组(求区间和)

提交部分正确(70分)
分析:1.时间超限,需要在区间修改和查询修改(主要)
2.没开long long, 区间和超出int范围了.(次要)
代码如下:

#include<iostream>
using namespace std;
#define ls (now<<1)
#define rs (now<<1|1) 
#define ll long long
const int maxn=1e5+10;
//开四倍,上面的我画的二叉数你也看见了,最大也到24了。 
int tree[maxn<<2];
int read(){
    int s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
//建立二叉树,并读入a数组.(巧在不用创建a数组) 
void Built(int now,int l,int r){
   if(l==r){
   	//这里有点东西,不断进栈 区间长度是1说明该输入吗,而且就是按a数组顺序输入. 
   	  tree[now]=read();return ;
   }
   int mid=(l+r)>>1;
   //采用先序遍历 
   Built(ls,l,mid);
   Built(rs,mid+1,r);
   //退栈,没退前拿点东西再走吧,
   //父节点的值 是两个儿子节点值之和。 
   tree[now]=tree[now<<1]+tree[now<<1|1];
   return ;
}
//区间修改 
void update(int now,int l,int r,int ql,int qr,int v){
//l.r变化区间  ql,qr指定区间
	if(l==r){
		tree[now]+=v;return ;
	}
	int mid=(l+r)>>1;
	//指定区间定(思考逻辑)  ql>mid和 qr<mid+1肯定是不在我们指定的区间 
	//这么理解 其实是把l,r区间的不断挤压到ql,qr区间 直至相同
	if(ql<=mid) update(ls,l,mid,ql,qr,v);
	if(mid<qr) update(rs,mid+1,r,ql,qr,v);
	tree[now]=tree[ls]+tree[rs];
	return ;
}
//区间查询 
int query(int now,int l,int r,int ql,int qr){
  	//理解为,区间不断挤压的过程
	//在什么情况就可以不用查了或者说递归结束呢???
	// ql,qr区间覆盖了l,r就不用了递归了,直接出结果.
	//2021129  ql,qr固定,mid区间左边l-mid不要,区间右边mid-r不要。
	  //mid在区间内:都要,慢慢拆分。
	if(ql<=l&&qr>=r){
	  //只要查到了就行了 指定的区间包含l,r区间就可.
		return tree[now];
	}
	int mid=(l+r)>>1;
	int ans=0;
	if(ql<=mid) ans+=query(ls,l,mid,ql,qr);
	if(mid<qr)  ans+=query(rs,mid+1,r,ql,qr);
	return ans;  
}
int main (){
	int n;
	int m;
    cin>>n>>m;
    Built(1,1,n);
	int l,r,flag,v;
    for(int i=1;i<=m;i++){
      cin>>flag>>l>>r;
      if(flag==1){
        cin>>v;
        update(1,1,n,l,r,v);
	  }else{
    	cout<<query(1,1,n,l,r)<<endl;
	}
	}
	return 0;
}

优化:
tag数组标记二叉树节点
tag[i]:在树的i号节点对应的区间都加上tag[i]
tag在哪里做"文章"呢:不难看出没使用tag的线段树,每次区间修改的return返回之前时间复杂度到了o(nlogn) (凡是i节点的区间与指定修改的区间有交集就要改动,所以是o(nlogn))。用tag的话 可以缩短至log(n)
其想法是:只要指定修改的区间包含i节点的区间,用tag标记i节点。i的子节点就不用管了(啥时候管呢? 在下一次区间修改,查询时 需要用到该区间时,就标记下放,父节点标记归0。总结一句:当该区间要用到时tag就启动).
AC代码如下

#include<iostream>
using namespace std;
#define ls (now<<1)
#define rs (now<<1|1) 
#define ll long long
const int maxn=1e5+10;
//开四倍,上面的我画的二叉数你也看见了,最大也到24了。 
//tree数组和标记数组 
ll tree[maxn<<2],tag[maxn<<2]; 
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
//建立二叉树,并读入a数组.(巧在不用创建a数组) 
void Built(int now,int l,int r){
   if(l==r){
   	//这里有点东西,不断进栈 区间长度是1说明该输入吗,而且就是按a数组顺序输入. 
   	  tree[now]=read();return ;
   }
   int mid=(l+r)>>1;
   //采用先序遍历 
   Built(ls,l,mid);
   Built(rs,mid+1,r);
   //退栈,没退前拿点东西再走吧,
   //父节点的值 是两个儿子节点值之和。 
   tree[now]=tree[now<<1]+tree[now<<1|1];
   return ;
}
//标记下放 
void pushdown(int now,int l,int r){
	if(!tag[now]) return ;
	int mid=(l+r)>>1;
	//标记下放 
	tree[ls]+=tag[now]*(mid-l+1);
	tree[rs]+=tag[now]*(r-mid);
	//一定要记得标记儿子节点!!! 
	//你把我坑死了+= ps:- _ -; 
     tag[ls]+=tag[now];
     tag[rs]+=tag[now];
	//下放后清空父节点的标记 
	//父节点用了,就不要了,传给儿子了,父亲tag自然就没了蛮 
	tag[now]=0; 
}
//区间修改 
void update(int now,int l,int r,int ql,int qr,ll v){
    if(l>=ql&&r<=qr){
    	tree[now]+=v*(r-l+1);
    	tag[now]+=v;
      return ;
	}
	int mid=(l+r)>>1;
	//每次修改和查询都要标记下放 
	pushdown(now,l,r);
	if(ql<=mid) update(ls,l,mid,ql,qr,v);
	if(mid<qr) update(rs,mid+1,r,ql,qr,v);
	
	tree[now]=tree[ls]+tree[rs];
	//return ;
}
//区间查询 
ll query(int now,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr){
		return tree[now];
	}
	int mid=(l+r)>>1;
	ll ans=0;
		//每次修改和查询都要标记下放 
	pushdown(now,l,r);
	if(ql<=mid) ans+=query(ls,l,mid,ql,qr);
	if(mid<qr)  ans+=query(rs,mid+1,r,ql,qr);
	return ans;  
}
int main (){
	ll n;
	ll m;
    cin>>n>>m;
    Built(1,1,n);
	ll l,r,flag,v;
    for(int i=1;i<=m;i++){
      cin>>flag>>l>>r;
      if(flag==1){
        cin>>v;
        update(1,1,n,l,r,v);
	  }else{
    	cout<<query(1,1,n,l,r)<<endl;
	}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axtices

谢谢您的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值