A Simple Problem with Integers POJ - 3468 线段树学习-lazy标签 模板

题目链接

A Simple Problem with Integers

题目大意:

  poj 3468
  
  给出N个数,进行Q个操作,1<=N,Q<=1e5.有两种操作:
  “C a b”,对区间[a,b]的每个数组加c;
  “Q a b”查询区间[a,b]的数字和。
  输入:N,Q,以及N个数字,Q个操作。
  输出:对每个查询的操作,输出结果。
  样例输入:
  10 5
  1 2 3 4 5 6 7 8 9 10
  Q 4 4
  Q 1 10
  Q 2 4
  C 3 6 3
  Q 2 4
  输出:
  4
  55
  9
  15 

解析:

复杂度分析

   容易知道暴力的话时间复杂度是 O ( n 2 ) O(n^2) O(n2) 。区间修改在线段树中如果一个点一个点修改的话,对一个点的修改是 l o g ( n ) log(n) log(n),对区间上的每个点修改就是 n ∗ l o g ( n ) n*log(n) nlog(n),有q次操作那么一共就是 n 2 ∗ l o g ( n ) n^2*log(n) n2log(n),还不如暴力法。

lazy-tag的方法:

  当我们要给一个区间的值都加上c的时候,我们不去修改这个区间里面所有的点,反而给这个一整个加上c而不向下深入。当某一次查询或者更新的时这个区间被破环时就要向下更新。

更新操作:

   比如你想给区间[3,6]里的数都加上一个数,那么对于区间[3,6],从根节点开始,递归查找区间[3,6] 有两种情况:与[3,6]交错,被[3,6] 包含,对于第二种就把这个区间上的和sum加上 c ∗ ( r − l + 1 ) c*(r-l+1) c(rl+1),用一个add数组标记lazy标签,其值加上c。(为什么这样加看下面代码注解)。
    对于第1种要继续深入,比如对于区间[6,10],它与区间[3,6]交错,如果区间[6,10]有lazy标签,那么就需要向下更新(因为在之前的某一次操作中子区间并没有更新,而现在需要向下深入)。

查询操作:

    比如对于查询区间[3,6]的区间和,那么对于区间[3,6],同样从根节点开始,递归查找区间[3,6] 同样有两种情况:与[3,6]交错,被[3,6] 包含,对于第一种那么就继续深入,同样可能引发向下更新。对于第二种区间直接返回这个区间的和,而不继续深入。

#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstdio>
#include<string> 
#include<string.h>
#include<list>
#include<queue>
#include<sstream>
#include<vector>
#include<set>
#include<map>
#include<deque>
#include<stack>
using namespace std;
#define debug(x) cout<<"###"<<x<<"###"<<endl;
const int INF=0x3f3f3f3f,mod=1e9,Maxn=4*1e6;//空间要4倍以免RE
typedef long long ll;
ll sum[Maxn],add[Maxn];//sum 区间和,add lazy标记
ll a[Maxn];
int n,cnt=1;
#define lson l, mid, rt<<1
#define rson mid+1, r, rt<<1|1
void push_up(int rt){
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];//向上更新,把子区间的信息向上传递
}
void push_down(int rt,int m){//说明区间被有交错,也就是lazy标签被破坏 m这个区间的长度
	if(add[rt]){//如果这个区间有lazy标签
		add[rt<<1]+=add[rt];// 传递标签给左孩子
		add[rt<<1|1]+=add[rt];//传递标签给右孩子
		sum[rt<<1]+=(m-(m>>1))*add[rt];//左孩子的sum更新,add加c的作用就是方便孩子sum的更新。
		sum[rt<<1|1]+=(m>>1)*add[rt];//右孩子的sum更新
		add[rt]=0;	//取消本层的标记
	}
}
void build(int l,int r,int rt){//建树
	add[rt]=0;
	if(l==r){
		sum[rt]=a[cnt];
		cnt++;
		return;
	}
	int mid=(l+r)>>1;
	build(lson);
	build(rson);
	push_up(rt);//向上更新返回区间和
}
void update(int a,int b,ll c,int l,int r,int rt){//更新区间
	if(a<=l&&b>=r){
		sum[rt]+=(r-l+1)*c;//区间被包含整体sum加上(r-l+1)*c
		add[rt]+=c;//加上c标记
		return;
	}
	push_down(rt,r-l+1);//向下更新
	int mid=(l+r)>>1;
	if(a<=mid){//分成两半继续深入
		update(a,b,c,lson);
	}
	if(b>mid){
		update(a,b,c,rson);
	}
	push_up(rt);//向上更新
}
ll query(int a,int b,int l,int r,int rt){
	if(a<=l&&b>=r)return sum[rt];//如果区间被包括。直接返回
	push_down(rt,r-l+1);//否则说明区间被破坏,有可能需要更新
	int mid=(l+r)>>1;//分成两半继续向下查找
	ll ans=0;
	if(a<=mid){
		ans+=query(a,b,lson);
	}
	if(b>mid)ans+=query(a,b,rson);
	return ans;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,n,1);
	while(m--){
		char s[2];
		int a,b;
		ll c;
		scanf("%s",s);
		if(s[0]=='C'){
			scanf("%d%d%lld",&a,&b,&c);
			update(a,b,c,1,n,1);//更新
		}else{
			scanf("%d%d",&a,&b);
			printf("%lld\n",query(a,b,1,n,1));
		}
	}
	return 0;
}

复杂度分析:

对于一个区间的查询与更新,复杂度是 l o g ( n ) log(n) log(n),有q次操作那么总的复杂度就是 n l o g ( n ) nlog(n) nlog(n)

  同时上面那个代码也是一个模板代码,很多题改一下就行了。

没有注释的板子
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstdio>
#include<string> 
#include<string.h>
#include<list>
#include<queue>
#include<sstream>
#include<vector>
#include<set>
#include<map>
#include<deque>
#include<stack>
using namespace std;
#define debug(x) cout<<"###"<<x<<"###"<<endl;
const int INF=0x3f3f3f3f,mod=1e9,Maxn=4*1e6;
typedef long long ll;
ll sum[Maxn],add[Maxn];
ll a[Maxn];
int n,cnt=1;
#define lson l, mid, rt<<1
#define rson mid+1, r, rt<<1|1
void push_up(int rt){
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void push_down(int rt,int m){
	if(add[rt]){
		add[rt<<1]+=add[rt];
		add[rt<<1|1]+=add[rt];
		sum[rt<<1]+=(m-(m>>1))*add[rt];
		sum[rt<<1|1]+=(m>>1)*add[rt];
		add[rt]=0;
	}
}
void build(int l,int r,int rt){
	add[rt]=0;
	if(l==r){
		sum[rt]=a[cnt];
		cnt++;
		return;
	}
	int mid=(l+r)>>1;
	build(lson);
	build(mid+1,r,rt<<1|1);
	push_up(rt);
}
void update(int a,int b,ll c,int l,int r,int rt){
	if(a<=l&&b>=r){
		sum[rt]+=(r-l+1)*c;
		add[rt]+=c;
		return;
	}
	push_down(rt,r-l+1);
	int mid=(l+r)>>1;
	if(a<=mid){
		update(a,b,c,lson);
	}
	if(b>mid){
		update(a,b,c,mid+1,r,rt<<1|1);
	}
	push_up(rt);
}
ll query(int a,int b,int l,int r,int rt){
	if(a<=l&&b>=r)return sum[rt];
	push_down(rt,r-l+1);
	int mid=(l+r)>>1;
	ll ans=0;
	if(a<=mid){
		ans+=query(a,b,lson);
	}
	if(b>mid)ans+=query(a,b,rson);
	return ans;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,n,1);
	while(m--){
		char s[2];
		int a,b;
		ll c;
		scanf("%s",s);
		if(s[0]=='C'){
			scanf("%d%d%lld",&a,&b,&c);
			update(a,b,c,1,n,1);
		}else{
			scanf("%d%d",&a,&b);
			printf("%lld\n",query(a,b,1,n,1));
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值