算法学习:线段树

线段树,个人理解,生成一棵二叉树,树上的节点表示区间的答案,因为二叉树的性质天然就将树分成两半,所以可以用每个节点存左半边右半边,然后这样子就可以保证效率。

具体讲解是看这位大大的博客,图解和语言都很详细。

https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html

这个里面树是靠结构体存储了一个真正意义上节点为包含左右区间范围及其答案的节点

然后我的代码是参考了洛谷的一个题解的代码,所以是用询问函数上的范围取代了,但是这两者区别并不是很大,因为p这个节点的数字,通过二进制来看,天然的就可以求取出他所代表的区间。

附代码,对树的解释都在注释上:

#include<cstdio>
#define ll long long
#define MAXN 100010
int a[MAXN];
int ans[MAXN];
int t[MAXN];
int tag[MAXN];
inline int ls(int p){return p<<1;}//找左节点 
inline int rs(int p){return p<<1|1;}//找右节点 
void push_up(int p)//更新操作 
{
	ans[p]=ans[ls(p)]+ans[rs(p)];
	return;
}
void build(ll p,ll l,ll r)//建树 
{
	if(l==r)//如果是底层叶节点 
	{		//l==r区间,也就是这个节点本身 
		ans[p]=a[l];	//返回叶节点 
		return;			//那么就是当前数组保存此节点 
	}
	ll mid=(l+r)>>1;
	build(ls(p),l,mid);//使p的左孩子包含(l---mid)的值 
	build(rs(p),mid+1,r);
	push_up(p); 
	//左右结点的值已经通过递归得到
	//可以根据左右确定(更新)自己的值 
	return;
}
void f(int p,int l,int r,int k)
{
	tag[p]+=k;
	//给当前节点加上之前节点的懒标记 
	//这样子懒标记不会下传
	//但是之后还可以用,不会影响其他的查询 
	ans[p]+=(r+1-l)*k;
	//更新树上的值
	//这样就不会向下扩散 
	//保证效率 
}
void push_down(int p,int l,int r)
{
	ll mid=(l+r)>>1;
	//向下更新 
	f(ls(p),l,mid,tag[p]);
	f(rs(p),mid+1,r,tag[p]);
	tag[p]=0;
	//因为这个点的懒标记已经下传
	//所以这个点对下面来说
	//已经没有了需要更新的值 
	//懒标记清0 
} 
//我个人最难理解的一步 
//对区间的更新 
//nr,nl需要查询的期间
//l,r当前节点 
void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
	if(nl<=l&&r<=nr)
	//若当前区间全部在所查询区间
	//直接进行更新 
		{
			ans[p]+=(r+1-l)*k;
			tag[p]+=k;
			return ;	 
		}	
	push_down(p,l,r); 
	//因为需要保证下面两个节点的正确性 
	//所以要向下进行更新 
	ll mid=(r+l)>>1;
	if(nl<=mid)	update(nl,nr,l,mid,ls(p),k);
	if(nr>mid)	update(nl,nr,mid+1,r,rs(p),k);
	//根据区间对子节点进行更新 
	//先将子节点更新完成 
	push_up(p);
	return;
} 
ll query(ll nl,ll nr,ll l,ll r,ll p)
{
	ll res=0;
	if(nl<=l&&r<=nr)
		{
			return ans[p];	 
		}	
	ll mid=(r+l)>>1;
	push_down(p,l,r);
	if(nl<=mid)	res+=query(nl,nr,l,mid,ls(p));
	if(nr>mid)	res+=query(nl,nr,mid+1,r,rs(p));
	return	res;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
	build(1,1,n);
	while(m--)
	{
		int p;
		scanf("%d",&p);
		switch(p)
		{
			case 1:
				{
					int l,r,k;
					scanf("%d%d%d",&l,&r,&k);
					update(l,r,1,n,1,k);
					//可以理解为对第一个节点进行更新
					//第一个节点包括的区间就是1~n 
					break;				//修改操作 
				}
			case 2:
				{
					int l,r;
					scanf("%d%d",&l,&r);
					printf("%lld\n",query(l,r,1,n,1));//输出值的操作 
					break;//1,n代表的是第一个节点,代表的是具有从1~n的值的数字 
				}
		}
	}
	return 0;
}

以下是无注释版:

#include<cstdio>
#define ll long long
#define MAXN 100010
int a[MAXN];
int ans[MAXN];
int t[MAXN];
int tag[MAXN];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;} 
void push_up(int p)
{
	ans[p]=ans[ls(p)]+ans[rs(p)];
	return;
}
void build(ll p,ll l,ll r)
{
	if(l==r)	
    {		 
		ans[p]=a[l];	
		return;			 
	}
	ll mid=(l+r)>>1;
	build(ls(p),l,mid); 
	build(rs(p),mid+1,r);
	push_up(p); 
	return;
}
void f(int p,int l,int r,int k)
{
	tag[p]+=k;
	ans[p]+=(r+1-l)*k;
}
void push_down(int p,int l,int r)
{
	ll mid=(l+r)>>1;
	f(ls(p),l,mid,tag[p]);
	f(rs(p),mid+1,r,tag[p]);
	tag[p]=0;
} 
void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
	push_down(p,l,r); 
    if(nl<=l&&r<=nr) 
		{
			ans[p]+=(r+1-l)*k;
			tag[p]+=k;
			return ;	 
		}	
	ll mid=(r+l)>>1;
	if(nl<=mid)	update(nl,nr,l,mid,ls(p),k);
	if(nr>mid)	update(nl,nr,mid+1,r,rs(p),k);
	push_up(p);
	return;
} 
ll query(ll nl,ll nr,ll l,ll r,ll p)
{
	ll res=0;
    push_down(p,l,r); 
	if(nl<=l&&r<=nr)
		{
			return ans[p];	 
		}	
	ll mid=(r+l)>>1;
	if(nl<=mid)	res+=query(nl,nr,l,mid,ls(p));
	if(nr>mid)	res+=query(nl,nr,mid+1,r,rs(p));
	return	res;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
	build(1,1,n);
	while(m--)
	{
		int p;
		scanf("%d",&p);
		switch(p)
		{
			case 1:
				{
					int l,r,k;
					scanf("%d%d%d",&l,&r,&k);
					update(l,r,1,n,1,k);
					break;			
				}
			case 2:
				{
					int l,r;
					scanf("%d%d",&l,&r);
					printf("%lld\n",query(l,r,1,n,1));
					break; 
				}
		}
	}
	return 0;
}

 

题目练习:

[AHOI 2009] 维护序列

思路:实际上还是道板子题,只不过加入了乘法的应用,对加法的操作还是和之前一样,但是乘法运算时,还要对加法运算的懒标记进行更新,注意push_down函数的位置,以及各种小问题,(因为各种小问题卡了好久)

#include<cstdio>
#include<iostream>
#define ll long long
#define MAXN 100010
using namespace std;
ll a[MAXN];
ll ans[MAXN<<2];
ll res;
ll tag_m[MAXN<<2],tag_a[MAXN<<2];
ll mod,n,m;
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;} 
void push_up(int p)
{
	ans[p]=(ans[ls(p)]+ans[rs(p)])%mod;
	return;
}
void build(ll p,ll l,ll r)
{
	if(l==r)	
    {		 
		ans[p]=a[l];	
		return;			 
	}
	ll mid=(l+r)>>1;
	build(ls(p),l,mid); 
	build(rs(p),mid+1,r);
	push_up(p); 
	return;
}
void push_down(int p,int l,int r,int op)
{
	if(tag_m[p]==1&&tag_a[p]==0)
		return;
	ll lp=ls(p),rp=rs(p);
	if(l!=r)
		{
			tag_m[rp]=tag_m[rp]*tag_m[p]%mod;
			tag_m[lp]=tag_m[lp]*tag_m[p]%mod;
			tag_a[lp]=((tag_a[lp]*tag_m[p])%mod+tag_a[p])%mod;
			tag_a[rp]=((tag_a[rp]*tag_m[p])%mod+tag_a[p])%mod;
		}	
	ans[p]=(ans[p]*tag_m[p]%mod+tag_a[p]*(r-l+1)%mod)%mod;
	tag_m[p]=1;tag_a[p]=0;
} 
void update(ll nl,ll nr,ll l,ll r,ll p,ll k,int op)
{	
	push_down(p,l,r,op);
	if(nl<=l&&r<=nr) 
		{
			switch(op)
				{
					case 1:tag_m[p]=(tag_m[p]*k)%mod,tag_a[p]=(tag_a[p]*k)%mod;break;
					case 2:tag_a[p]=(tag_a[p]+k)%mod;break;
				}	
			return ;	 
		}	
	ll mid=(r+l)>>1;
	if(nl<=mid)	update(nl,nr,l,mid,ls(p),k,op);
	if(nr>mid)	update(nl,nr,mid+1,r,rs(p),k,op);
	push_down(ls(p),l,mid,op);
	push_down(rs(p),mid+1,r,op);
	push_up(p);
	return;
} 
ll query(ll nl,ll nr,ll l,ll r,ll p,int op)
{
	ll res=0;
	push_down(p,l,r,op);
	if(nl<=l&&r<=nr)
		{
			return ans[p];	 
		}	
	ll mid=(r+l)>>1;	
	if(nl<=mid)	res+=query(nl,nr,l,mid,ls(p),op);
	if(nr>mid)	res+=query(nl,nr,mid+1,r,rs(p),op);
	return	res;
}
int main()
{
	scanf("%lld%lld",&n,&mod);
	for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
		}
	for(int i=1;i<=2*n;i++)
		{
			tag_m[i]=1;
		}
	build(1,1,n);
	scanf("%lld",&m);
	for(int i=1;i<=m;i++)	
		{
			int op,l,r,k;
			scanf("%d",&op);
			switch(op)
				{
				
					case 1:
					case 2:scanf("%d%d%d",&l,&r,&k);
						   update(l,r,1,n,1,k,op);
						   break;
					case 3:scanf("%d%d",&l,&r);
						  res=query(l,r,1,n,1,op)%mod;
					 	  printf("%lld\n",res);
						  break;
				}
		}
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值