线段树----单点、区间加法,单点、区间覆盖,区间最大最小值
线段树基本知识
详解可以看这个:https://www.luogu.com.cn/problemnew/solution/P3372
摘录了一点点。。。
对于每一个子节点而言,都表示整个序列中的一段子区间;对于每个叶子节点而言,都表示序列中的单个元素信息;子节点不断向自己的父亲节点传递信息,而父节点存储的信息则是他的每一个子节点信息的整合。
有没有觉得很熟悉?对,线段树就是分块思想的树化,或者说是对于信息处理的二进制化——用于达到O(logn)O(logn)级别的处理速度,loglog以22为底。(其实以几为底都只不过是个常数,可忽略)。而分块的思想,则是可以用一句话总结为:通过将整个序列分为有穷个小块,对于要查询的一段区间,总是可以整合成kk个所分块与mm个单个元素的信息的并(0<=k,m<=\sqrt{n})(0<=k,m<= n )。但普通的分块不能高效率地解决很多问题,所以作为loglog级别的数据结构,线段树应运而生。
建树过程
void build(ll rt,ll l,ll r)
{
if(l==r)
{
st[rt]=a[l];
return;
}
ll mid=(r+l)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
根据二叉树的特性,从1~N的节点按二叉树排列,对于某一父节点来说,他的左右子节点分别是:i2,i2-1。
于是,用位运算表示为 i<<1和i<<1|1。
那么当我们不断二分我们的区间时,到了区间左端点和右端点相等的时候就找到了我们需要真实赋值的叶子节点。
这是一种递归的方式,当递归结束开始回溯是,不断根据子节点的值维护父节点。由于这是求区间和的线段树,所以父节点的值是两个儿子节点值的和。
inline void pushup(ll rt)
{
st[rt]=st[rt<<1]+st[rt<<1|1];
}
区间修改
注:由于习惯问题,我把懒标记和节点值打包成了一个结构体。
st[i].v是该节点的值,st[i].lazy是该节点的懒标记。
void pushdown(ll rt,ll l,ll r)
{
if(st[rt].lazy)
{
ll mid=(l+r)>>1;
st[rt<<1].v+=(mid-l+1)*st[rt].lazy;
st[rt<<1|1].v+=(r-mid)*st[rt].lazy;
st[rt<<1].lazy+=st[rt].lazy;
st[rt<<1|1].lazy+=st[rt].lazy;
st[rt].lazy=0;
}
}
void update(ll rt,ll l,ll r,ll ql,ll qr,ll xx)
{
if(ql<=l&&qr>=r)
{
st[rt].lazy+=xx;
st[rt].v+=(r-l+1)*xx;
return;
}
pushdown(rt,l,r);
ll mid=(l+r)>>1;
if(ql<=mid) update(rt<<1,l,mid,ql,qr,xx);
if(qr>mid) update(rt<<1|1,mid+1,r,ql,qr,xx);
pushup(rt);
}
这里引入了懒标记这个东西(lazytag)
用处是这样子:
整个区间都被操作,记录在公共祖先节点上,并且只修改该节点的值(咋修改不用我说了吧,就是加上这个值×区间长度);只修改了一部分,那么就记录在这部分的公共祖先上;如果四环以内只修改了自己的话,那就只改变自己。
所以当我们每次递归查询每个节点的时候都要注意一下该节点的祖先节点是不是有懒标记。如果有的话就修改该节点并下推一层。
回溯时记得直接维护好父节点,就是那个pushup。就算这样也比直接改每一个点快的多的多的多的多。
求区间和
ll query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return st[rt].v;
}
ll mid=(l+r)>>1;
ll res=0;
pushdown(rt,l,r);
if(ql<=mid) res+=query(rt<<1,l,mid,ql,qr);
if(qr>mid) res+=query(rt<<1|1,mid+1,r,ql,qr);
return res;
}
求和过程如上,还是看懒标记。
对于那两个if
这里的解释是这样的:https://blog.csdn.net/wodasini/article/details/79885275
a) 朴素法
在构造时,对于每个节点保存左右区间的范围。
然后递归寻找Query(root,L,R)
分三类情况:
完全在左区间 Query(lson,L,R)
完全在右区间 Query(rson,L,R)
区间跨越范围 Query(lson,L,Mid),Query(rson,Mid+1,r)
b) 全局法:
在构造时:不需要考虑每个节点的范围
根据Query函数调用时的区间和查询所用的区间直接判断。
设L,R是当前调用询问的区间;qL,qR是查询区间
还是分二类情况:若[L,R] 包含于 [qL,qR] 直接考虑当前root的值
否则考虑qR,qL与Mid的关系
若qL <= Mid 考虑Query(lson,qL,Mid)
若Mid < qR 则考虑Query(rson,Mid+1,qR)
两个if这种全局法要稍微快一些。最重要的是写得少!!
单点修改没什么好说的,就递归找到那个l==r 的点直接修改然后向上维护父节点即可
void update(ll rt,ll l,ll r,ll pos,ll x)// 单点修改,单点覆盖同理
{
if(l==r)
{
st[rt]+=x;// 改成st[rt]=x就是覆盖
return;
}
ll mid=(l+r)>>1;
if(pos<=mid)
{
update(rt<<1,l,mid,pos,x);
}
if(pos>mid)
{
update(rt<<1|1,mid+1,r,pos,x);
}
pushup(rt);
}
点覆盖及区间最大最小值
其实都差不多,基本的思路就是那样。
点覆盖看上面,就一个加号的事情嘛
只有最大最小值要注意,在查找的时候,是分两边递归的,那么最后返回答案的时候两边的最大值或者最小值需要再比一遍。这里在集成到一起的时候就稍微有些麻烦(如果有更简单的我会再找找的)
#define ll long long
#define maxn 500007
#define il inline
ll a[maxn];
struct node{
ll v,maxx,minn;
}st[maxn*4];
il void pushup(ll rt)
{
st[rt].v=st[rt<<1].v+st[rt<<1|1].v;
st[rt].maxx=max(st[rt<<1].maxx,st[rt<<1|1].maxx);
st[rt].minn=min(st[rt<<1].minn,st[rt<<1|1].minn);
}
il void build(ll rt,ll l,ll r)
{
if(l==r)
{
st[rt].v=a[l];
st[rt].maxx=a[l];
st[rt].minn=a[l];
return;
}
ll mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
il void update(ll rt,ll l,ll r,ll pos,ll x)
{
if(l==r)
{
st[rt].v=x;
st[rt].maxx=x;
st[rt].minn=x;
return;
}
ll mid=(l+r)>>1;
if(pos<=mid)
{
update(rt<<1,l,mid,pos,x);
}
if(pos>mid)
{
update(rt<<1|1,mid+1,r,pos,x);
}
pushup(rt);
}
il node query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
node tmp;
tmp.v=st[rt].v;
tmp.maxx=st[rt].maxx;
tmp.minn=st[rt].minn;
return tmp;
}
ll mid=(l+r)>>1;
node res1,res2,res;
res1.v=0,res2.v=0,res.v=0;
res1.maxx=-10000007,res2.maxx=-10000007,res.maxx=-10000007;//这个和题目有关,为了能找到合适的值就要把maxx设置的特别小,好在找的时候变大,你设个0,万一有个负数不就GG了。。
res1.minn=0x3f3f3f3f,res2.minn=0x3f3f3f3f,res.minn=0x3f3f3f3f;// 同理
if(ql<=mid)
{
res1=query(rt<<1,l,mid,ql,qr);
res.v+=res1.v;
}
if(qr>mid)
{
res2=query(rt<<1|1,mid+1,r,ql,qr);
res.v+=res2.v;
}
res.maxx=max(res1.maxx,res2.maxx);
res.minn=min(res1.minn,res2.minn);
return res;
}
区间覆盖
也是用懒标记记录一下,直接改修改区间内的公共父节点,改成修改值×区间长度
有个问题,但万一出现交叉的情况怎么办?下传标记!
假如你有一区间A 已经被打上懒标记了,此时有一修改涉及到A的一段子区间B,这时候你应该先下下传A原来的标记,然后修改B区间,并在B区间打上新的标记。这样就能保证数据的真实性。
il void pushdown(ll rt,ll l,ll r)
{
if(st[rt].lazy!=inf)
{
ll mid=(l+r)>>1;
st[rt<<1].v=(mid-l+1)*st[rt].lazy;
st[rt<<1|1].v=(r-mid)*st[rt].lazy;
st[rt<<1].lazy=st[rt].lazy;
st[rt<<1|1].lazy=st[rt].lazy;
st[rt].lazy=inf;
}
}
il void change(ll rt,ll l,ll r,ll ql,ll qr,ll x)
{
if(ql<=l&&qr>=r)
{
st[rt].v=(r-l+1)*x;
st[rt].lazy=x;
return;
}
pushdown(rt,l,r);
ll mid=(l+r)>>1;
if(ql<=mid)
{
change(rt<<1,l,mid,ql,qr,x);
}
if(qr>mid)
{
change(rt<<1|1,mid+1,r,ql,qr,x);
}
pushup(rt);
}
il ll query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return st[rt].v;
}
ll mid=(r+l)>>1;
ll res=0;
pushdown(rt,l,r);
if(ql<=mid)
{
res+=query(rt<<1,l,mid,ql,qr);
}
if(qr>mid)
{
res+=query(rt<<1|1,mid+1,r,ql,qr);
}
return res;
}
ECNU oj 3389 3390 3392 3392
3389 点增加 (HDU 1166)
//EDNU 3390 HDU 1166
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
#define maxn 500007
ll a[maxn],st[maxn*4];
inline void pushup(ll rt)
{
st[rt]=st[rt<<1]+st[rt<<1|1];
}
void build(ll rt,ll l,ll r)
{
if(l==r)
{
st[rt]=a[l];
return;
}
ll mid=(r+l)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
void update(ll rt,ll l,ll r,ll pos,ll x)
{
if(l==r)
{
st[rt]+=x;
return;
}
ll mid=(l+r)>>1;
if(pos<=mid)
{
update(rt<<1,l,mid,pos,x);
}
if(pos>mid)
{
update(rt<<1|1,mid+1,r,pos,x);
}
pushup(rt);
}
ll query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return st[rt];
}
ll mid=(r+l)>>1;
ll res=0;
if(ql<=mid) res+=query(rt<<1,l,mid,ql,qr);
if(qr>mid) res+=query(rt<<1|1,mid+1,r,ql,qr);
return res;
}
int main()
{
ll m,n;
cin>>n;
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
cin>>m;
while(m--)
{
int flag;
ll xx,yy;
scanf("%d %lld %lld",&flag,&xx,&yy);
if(flag==1)
{
update(1,1,n,xx,yy);
}
if(flag==2)
{
printf("%lld\n",query(1,1,n,xx,yy));
}
}
return 0;
}
3390 点覆盖(+区间最大最小值)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
#define maxn 500007
#define il inline
ll a[maxn];
struct node{
ll v,maxx,minn;
}st[maxn*4];
il void pushup(ll rt)
{
st[rt].v=st[rt<<1].v+st[rt<<1|1].v;
st[rt].maxx=max(st[rt<<1].maxx,st[rt<<1|1].maxx);
st[rt].minn=min(st[rt<<1].minn,st[rt<<1|1].minn);
}
il void build(ll rt,ll l,ll r)
{
if(l==r)
{
st[rt].v=a[l];
st[rt].maxx=a[l];
st[rt].minn=a[l];
return;
}
ll mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
il void update(ll rt,ll l,ll r,ll pos,ll x)
{
if(l==r)
{
st[rt].v=x;
st[rt].maxx=x;
st[rt].minn=x;
return;
}
ll mid=(l+r)>>1;
if(pos<=mid)
{
update(rt<<1,l,mid,pos,x);
}
if(pos>mid)
{
update(rt<<1|1,mid+1,r,pos,x);
}
pushup(rt);
}
il node query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
node tmp;
tmp.v=st[rt].v;
tmp.maxx=st[rt].maxx;
tmp.minn=st[rt].minn;
return tmp;
}
ll mid=(l+r)>>1;
node res1,res2,res;
res1.v=0,res2.v=0,res.v=0;
res1.maxx=-10000007,res2.maxx=-10000007,res.maxx=-10000007;
res1.minn=0x3f3f3f3f,res2.minn=0x3f3f3f3f,res.minn=0x3f3f3f3f;
if(ql<=mid)
{
res1=query(rt<<1,l,mid,ql,qr);
res.v+=res1.v;
}
if(qr>mid)
{
res2=query(rt<<1|1,mid+1,r,ql,qr);
res.v+=res2.v;
}
res.maxx=max(res1.maxx,res2.maxx);
res.minn=min(res1.minn,res2.minn);
return res;
}
int main()
{
int n,m;
cin>>n;
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
cin>>m;
while(m--)
{
int flag;
ll xx,yy;
scanf("%d %lld %lld",&flag,&xx,&yy);
if(flag==1)
{
update(1,1,n,xx,yy);
}
if(flag==2)
{
node ans=query(1,1,n,xx,yy);
printf("%lld %lld %lld\n",ans.v,ans.maxx,ans.minn);
}
}
return 0;
}
3391 区间增加
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
#define maxn 500007
#define ls <<1
#define rs o<<1|1
#define il inline
ll a[maxn];
struct node{
ll v,lazy;
}st[maxn*4];
il void pushup(ll rt)
{
st[rt].v=st[rt<<1].v+st[rt<<1|1].v;
}
void build(ll rt,ll l ,ll r)
{
st[rt].lazy=0;
if(l==r)
{
st[rt].v=a[l];
return;
}
ll mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
void pushdown(ll rt,ll l,ll r)
{
if(st[rt].lazy)
{
ll mid=(l+r)>>1;
st[rt<<1].v+=(mid-l+1)*st[rt].lazy;
st[rt<<1|1].v+=(r-mid)*st[rt].lazy;
st[rt<<1].lazy+=st[rt].lazy;
st[rt<<1|1].lazy+=st[rt].lazy;
st[rt].lazy=0;
}
}
void update(ll rt,ll l,ll r,ll ql,ll qr,ll xx)
{
if(ql<=l&&qr>=r)
{
st[rt].lazy+=xx;
st[rt].v+=(r-l+1)*xx;
return;
}
pushdown(rt,l,r);
ll mid=(l+r)>>1;
if(ql<=mid) update(rt<<1,l,mid,ql,qr,xx);
if(qr>mid) update(rt<<1|1,mid+1,r,ql,qr,xx);
pushup(rt);
}
ll query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return st[rt].v;
}
ll mid=(l+r)>>1;
ll res=0;
pushdown(rt,l,r);
if(ql<=mid) res+=query(rt<<1,l,mid,ql,qr);
if(qr>mid) res+=query(rt<<1|1,mid+1,r,ql,qr);
return res;
}
int main()
{
ll n,m;
cin>>n;
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
cin>>m;
while(m--)
{
int flag;
ll x,y,z;
scanf("%d",&flag);
if(flag==1)
{
scanf("%lld %lld %lld",&x,&y,&z);
update(1,1,n,x,y,z);
}
if(flag==2)
{
scanf("%lld %lld",&x,&y);
printf("%lld\n",query(1,1,n,x,y));
}
}
return 0;
}
3392区间覆盖
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
#define maxn 500007
#define il inline
const ll inf=0x3f3f3f3f;
struct node{
ll v;
ll lazy;
}st[maxn*4];
ll a[maxn];
il void pushup(ll rt)
{
st[rt].v=st[rt<<1].v+st[rt<<1|1].v;
}
il void build(ll rt,ll l,ll r)
{
st[rt].lazy=inf;
if(l==r)
{
st[rt].v=a[l];
return;
}
ll mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
il void pushdown(ll rt,ll l,ll r)
{
if(st[rt].lazy!=inf)
{
ll mid=(l+r)>>1;
st[rt<<1].v=(mid-l+1)*st[rt].lazy;
st[rt<<1|1].v=(r-mid)*st[rt].lazy;
st[rt<<1].lazy=st[rt].lazy;
st[rt<<1|1].lazy=st[rt].lazy;
st[rt].lazy=inf;
}
}
il void change(ll rt,ll l,ll r,ll ql,ll qr,ll x)
{
if(ql<=l&&qr>=r)
{
st[rt].v=(r-l+1)*x;
st[rt].lazy=x;
return;
}
pushdown(rt,l,r);
ll mid=(l+r)>>1;
if(ql<=mid)
{
change(rt<<1,l,mid,ql,qr,x);
}
if(qr>mid)
{
change(rt<<1|1,mid+1,r,ql,qr,x);
}
pushup(rt);
}
il ll query(ll rt,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&qr>=r)
{
return st[rt].v;
}
ll mid=(r+l)>>1;
ll res=0;
pushdown(rt,l,r);
if(ql<=mid)
{
res+=query(rt<<1,l,mid,ql,qr);
}
if(qr>mid)
{
res+=query(rt<<1|1,mid+1,r,ql,qr);
}
return res;
}
int main()
{
int n,m;
cin>>n;
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
cin>>m;
while(m--)
{
int flag;
ll xx,yy,zz;
scanf("%d",&flag);
if(flag==1)
{
scanf("%lld %lld %lld",&xx,&yy,&zz);
change(1,1,n,xx,yy,zz);
}
if(flag==2)
{
scanf("%lld %lld",&xx,&yy);
printf("%lld\n",query(1,1,n,xx,yy));
}
}
return 0;
}
3393 区间覆盖+区间增加+区间最大值最小值(我也不会)
//????