线段树的本质
基于分治思想的二叉树
线段树的基本操作
1.建树
- 节点——————结构体(标号 / / / / / / 左侧,右侧,题目相关信息点)
- 递归边界————( l==r ) 到达叶节点
- 未到边界————分向两个子节点
- 回溯——————信息点
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.单点修改
- 从根节点向下走
- 递归边界————(l==r) 到达目标节点 直接修改
- 未到边界————分向目标所在子节点
- 回溯——————重新建树
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.区间查询
- 从根节点向下走
- 递归边界————节点p区间完全在(l,r)范围内 直接返回信息点
- 未到边界————l 与 mid 比较 l<=mid 左节点在范围内 r 与 mid 比较 r>mid 右节点在范围内
- 回溯——————信息点啊
就放一个图吧,画图能力有限啊~~~
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~~我们引入一个叫做 延迟标记 的高级配件 :
回头之前,修改该点信息 ,还差一句 “XXX到此一游” 即为 “该节点曾被修改,但其子节点还未修改” ,打道回府;
3. 细说延迟标记的功用 :
在这个只问结果不问过程 , 打表也能出奇迹的领域 ,修改的目的是为了保证我们输出询问答案的准确性 ,如果我们用了O(n)的时间进行了修改,但后面的询问根本就没看他一眼 ,那么恭喜你 ,白费劲啦 ,所以延迟标记的延迟就是指将修改延迟到询问到这个地方再进行 ,类比 再向下修改时也可以将延迟的修改执行
简单总结一下 : 区间修改
- 从根节点往下走
- 遇到延迟标记————向下修改节点 ,直到走到递归边界 ;
- 到达递归边界 ————修改该点信息 并 打上延迟标记 ,return ;
- 回溯————————相关信息
查询 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噢噢噢噢