重学线段树

线段树的本质

基于分治思想的二叉树  

线段树的基本操作

1.建树

  1. 节点——————结构体(标号 / / / / / /  左侧,右侧,题目相关信息点)
  2. 递归边界————( l==r )   到达叶节点 
  3. 未到边界————分向两个子节点
  4. 回溯——————信息点
int n;
int a[100005]={0};
struct Tree
{
	int l,r;
	int sum;
	int maxx;
}t[4*100000+5];

void build(int p,int l,int r)//p节点构造 
{
	t[p].l=l;t[p].r=r;                  //节点p代表[l,r]区间
	if(l==r)      { t[p].sum=a[l]; return ;}   //叶节点p 
	else 
	{
		int mid=(l+r)/2;
		build(p*2,l,mid); 
		build(p*2+1,mid+1,r);
		t[p].sum=t[p*2].sum+t[p*2+1].sum;
		//t[p].maxx=max(t[p*2].maxxt[p*2+1].maxx);
	}	
} 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)   scanf("%d",&a[i]);
	
	build(1,1,n);//从根节点开始构造 
    
    return 0;
}

2.单点修改

  1. 从根节点向下走
  2. 递归边界————(l==r) 到达目标节点  直接修改
  3. 未到边界————分向目标所在子节点  
  4. 回溯——————重新建树
void change(int p,int x,int y)//从根节点向下走,x节点改成y   //单点修改 
{
	 if(t[p].l==t[p].r)  { t[p].maxx=a[l]=y; return ;}
	 else 
	 {
	 	 int mid=(t[p].l+t[p].r)/2;
	 	 if(x<=mid)  change(p*2,x,y);
		 else        change(p*2+1,x,y);
		 t[p].sum=t[p*2].sum+t[p*2+1].sum;
		 //t[p].maxx=max(t[p*2].maxx,t[p*2+1].maxx);
	 }
} 

单点查询

3.区间查询

  1. 从根节点向下走
  2. 递归边界————节点p区间完全在(l,r)范围内  直接返回信息点
  3. 未到边界————l 与 mid 比较  l<=mid  左节点在范围内     r 与 mid 比较 r>mid 右节点在范围内
  4. 回溯——————信息点啊

就放一个图吧,画图能力有限啊~~~

int ask(int p,int l,int r)//从根节点往下走 ,找到 l 到 r 包含的区间   //区间查询 
{ 
	if(l<=t[p].l && r>=t[p].r)  { return t[p].sum;}
	else 
	{
		int re=0;
		int mid=(t[p].l+t[p].r)/2;
		if(l<=mid)  re+=ask(p*2,l,r);
		if(r>mid)   re+=ask(p*2+1,l,r);
		return re;
		/*
		int val=-(1<<30);//负无穷 
		if(l<=mid)  val=max( val,ask(p*2,l,r) );
		if(r>mid)   val=max( val,ask(p*2+1,l,r) );
		return val;
		*/
	}
}

4.区间修改

对于这个东东,我们先来思考思考

按照以上区间查询及单点修改的套路,我们从根节点向下走,p节点整个包含在范围内时(到达递归边界),修改信息

停一停,我们需要思考,咳~~

1. 这次修改信息 意味着p  节点以下的子树的信息点都要进行修改 ,那我们用线段树抢来的O(nlog n )时间复杂度就回到了O(n*n)的落后时代 ,时间就是金钱 ,为了O(Nlog N )的时间复杂度 ,坚决不能往下走去一个个修改 ;

2. 前路茫茫回头是岸啊~~ ,但二话不说就 return ;当然是不行滴 ,SO~~我们引入一个叫做  延迟标记 的高级配件 :

扫描二维码关注公众号,回复: 2292284 查看本文章

回头之前,修改该点信息 ,还差一句 “XXX到此一游”  即为 “该节点曾被修改,但其子节点还未修改” ,打道回府;

3. 细说延迟标记的功用 :

在这个只问结果不问过程 , 打表也能出奇迹的领域 ,修改的目的是为了保证我们输出询问答案的准确性 ,如果我们用了O(n)的时间进行了修改,但后面的询问根本就没看他一眼 ,那么恭喜你 ,白费劲啦 ,所以延迟标记的延迟就是指将修改延迟到询问到这个地方再进行 ,类比 再向下修改时也可以将延迟的修改执行

简单总结一下 : 区间修改   

  1.  从根节点往下走
  2.  遇到延迟标记————向下修改节点 ,直到走到递归边界 ;
  3.  到达递归边界 ————修改该点信息 并 打上延迟标记 ,return ;
  4.  回溯————————相关信息

  查询  1.从根节点向下走 

           2.遇到延迟标记————向下修改节点 ,直到走到递归边界 ;

           3.递归边界——————若下面还有节点 ,在该点打上延迟标记,回溯相关查询信息即可

       

注意 不要覆盖已有的标记 ,应该根据具体情况结合两个标记  例如:sum  +

struct Tree
{
	int l,r;
	int sum;
	int maxx;
	int add;//延迟标记 加为例 
	void Tree(){add=0;}  //请注意

}t[4*100000+5];
int ask(int p,int l,int r)//从根节点往下走 ,找到 l 到 r 包含的区间   //区间查询 
{ 
	if(l<=t[p].l && r>=t[p].r)  { return t[p].sum;}
	else 
	{
		spread(p);//请注意
		int re=0;
		int mid=(t[p].l+t[p].r)/2;
		if(l<=mid)  re+=ask(p*2,l,r);
		if(r>mid)   re+=ask(p*2+1,l,r);
		return re;
	
	}
}

void spread(int p)//下传标记
{
	if(t[p].add)
	{
		int mid=(t[p].l+t[p].r)/2;
		t[p*2].sum+=(long long)t[p].add*(t[p*2].r-t[p*2].l+1);
		t[p*2+1].sum+=(long long)t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
		t[p*2].add+=t[p].add;  //请注意
		t[p*2+1].add+=t[p].add; //请注意
		t[p].add=0;
	}
}

void change_q(int p,int l,int r,int v)
{
	if(t[p].l<=l && t[p].r>=r)  
	{
		t[p].sum+=(long long)v*(t[p].r-t[p].l+1);
	    t[p].add=v;
		return ; 
	}
	else
	{
		spread(p); //请注意
		int mid=(t[p].l+t[p].r)/2;
		if(l<=mid)  change_q(p*2,l,r,v);
		if(r>mid)   change_q(p*2+1,l,r,v);
		t[p].sum=t[p*2].sum+t[p*2+1].sum;
	}
}

P3372 【模板】线段树 1  AC版本

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
long long n,m;
long long  a[100005]={0};
struct Tree
{ 
    long long l,r;
	long long sum;
    long long  add;//延迟标记 加为例 
	Tree()  {add=0;sum=0;  return ;}

}t[400005];

void build(long long p,long long l,long long r)//p节点构造 
{
	t[p].l=l;t[p].r=r;                  //节点p代表[l,r]区间
	if(l==r)      { t[p].sum=a[l]; return ;}   //叶节点p 
	else 
	{
		long long mid=(l+r)/2;
		build(p*2,l,mid); 
		build(p*2+1,mid+1,r);
		t[p].sum=t[p*2].sum+t[p*2+1].sum;
	
	}	
} 

void spread(long long p)
{
	if(t[p].add)
	{
		long long mid=(t[p].l+t[p].r)/2;
		t[p*2].sum+=t[p].add*(t[p*2].r-t[p*2].l+1);
		t[p*2+1].sum+=t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
		t[p*2].add+=t[p].add;
		t[p*2+1].add+=t[p].add;
		t[p].add=0;
	}
}

long long ask(long long p,long long l,long long r)//从根节点往下走 ,找到 l 到 r 包含的区间   //区间查询 
{ 
	if(l<=t[p].l && r>=t[p].r)  { return t[p].sum;}
	else 
	{
		spread(p);
		long long re=0;
		long long mid=(t[p].l+t[p].r)/2;
		if(l<=mid)  re+=ask(p*2,l,r);
		if(r>mid)   re+=ask(p*2+1,l,r);
		return re;
	
	}
}

void change_q(long long p,long long l,long long r,long long v)
{
	if(l<=t[p].l && r>=t[p].r)  
	{
		t[p].sum+=v*(t[p].r-t[p].l+1);
	    t[p].add=v;
		return ; 
	}
	else
	{
		spread(p);
		long long mid=(t[p].l+t[p].r)/2;
		if(l<=mid)  change_q(p*2,l,r,v);
		if(r>mid)   change_q(p*2+1,l,r,v);
		t[p].sum=t[p*2].sum+t[p*2+1].sum;cout<<t[p].sum<<endl;
		
	}
}

int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)   scanf("%lld",&a[i]);
	
	build(1,1,n);//从根节点开始构造 
    long long z,x,y,v;
    while(m--)
    {
    	scanf("%lld",&z);
    	if(z==1)
    	{
    		scanf("%lld%lld%lld",&x,&y,&v);
    		change_q(1,x,y,v);
    	}
    	else
    	{
    		scanf("%lld%lld",&x,&y);
    		printf("%lld\n",ask(1,x,y));
    	}
    	
    }
    return 0;
}

P3373 【模板】线段树 2  等我AC噢噢噢噢

猜你喜欢

转载自blog.csdn.net/qq_42146446/article/details/81110533
今日推荐