一直想学线段树,但因为懒,一直没有时间,今天终于AC了洛谷P3372 线段树1,写篇博客加强一下理解。
线段树是什么?
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。时间复杂度为\(O(nlogn)\) ————百度百科
它有什么用?
线段树可以快速的处理一些可以“相加”的区间问题,它可以说是NOIp考纲里最强大的数据结构
既然它这么强,那现在就让我们来学习一下线段树吧!(>▽<)
线段树是用一个节点来保存一段区间,这样在我们查询区间的时候就不用一个一个枚举,而是直接查询对应的节点,比如上面的图,如果我们要查询编号为3~8的叶子节点,就只要查询3、(4~5)、(6~8)这三个节点就可以了,大大减少了时间复杂度。同时,线段树也支持区间修改,但为了保持线段树的高效性,它肯定不是一个一个枚举叶子节点,而是给要修改的节点打一个标记,俗称Lazy Tag,这样我们在修改的时候就可以直接调用Lazy Tag,而不是再查找一遍。
线段树的具体实现方法是这样的:
- 左右两个儿子:因为线段树是一棵二叉树,所以我们可以直接用预处理方便的实现
#define ls(x) x<<1
#define rs(x) x<<1|1
- push_up (用于维护父子关系,父节点=左儿子+右儿子)
void up(ll pos)
{
tree[pos]=tree[ls(pos)]+tree[rs(pos)];
}
- 递归建树
void build(ll pos,ll l,ll r)
{
if(l==r)
{
tree[pos]=a[l];
return ;
}
ll mid=(r+l)>>1;
build(ls(pos),l,mid);
build(rs(pos),mid+1,r);
up(pos);
}
- 处理Lazy Tag(据说加\(inline\)会有玄学优化)
inline void f(ll pos,ll l,ll r,ll k)
{
tag[pos]+=k; tree[pos]+=k*(r-l+1);
}
- push_down (下放Lazy Tag)
void down(ll pos,ll l,ll r)
{
ll mid=(l+r)>>1;
f(ls(pos),l,mid,tag[pos]);
f(rs(pos),mid+1,r,tag[pos]);
tag[pos]=0;
}
- 然后是将以上几个函数结合起来的update
void update(ll nl,ll nr,ll l,ll r,ll pos,ll k)
{
if(nl<=l&&r<=nr)
{
tree[pos]+=k*(r-l+1);
tag[pos]+=k;
return ;
}
down(pos,l,r);
ll mid=(l+r)>>1;
if(nl<=mid) update(nl,nr,l,mid,ls(pos),k);
if(nr>mid) update(nl,nr,mid+1,r,rs(pos),k);
up(pos);
}
- ans(回答询问
据说用dalao的名字会RP++)
ll ans(ll x,ll y,ll l,ll r,ll pos)
{
ll rqy=0;
if(x<=l&&r<=y) return tree[pos];
ll mid=(l+r)>>1;
down(pos,l,r);
if(x<=mid) rqy+=ans(x,y,l,mid,ls(pos));
if(y>mid) rqy+=ans(x,y,mid+1,r,rs(pos));
return rqy;
}
最后是完整代码:
#include<iostream>
#include<cstdio>
#define ls(x) x<<1
#define rs(x) x<<1|1
#define ll long long
using namespace std;
ll tree[100001*4],a[100001],tag[100001*4];
void up(ll pos)
{
tree[pos]=tree[ls(pos)]+tree[rs(pos)];
}
void build(ll pos,ll l,ll r)
{
if(l==r)
{
tree[pos]=a[l];
return ;
}
ll mid=(r+l)>>1;
build(ls(pos),l,mid);
build(rs(pos),mid+1,r);
up(pos);
}
inline void f(ll pos,ll l,ll r,ll k)
{
tag[pos]+=k; tree[pos]+=k*(r-l+1);
}
void down(ll pos,ll l,ll r)
{
ll mid=(l+r)>>1;
f(ls(pos),l,mid,tag[pos]);
f(rs(pos),mid+1,r,tag[pos]);
tag[pos]=0;
}
void update(ll nl,ll nr,ll l,ll r,ll pos,ll k)
{
if(nl<=l&&r<=nr)
{
tree[pos]+=k*(r-l+1);
tag[pos]+=k;
return ;
}
down(pos,l,r);
ll mid=(l+r)>>1;
if(nl<=mid) update(nl,nr,l,mid,ls(pos),k);
if(nr>mid) update(nl,nr,mid+1,r,rs(pos),k);
up(pos);
}
ll ans(ll x,ll y,ll l,ll r,ll pos)
{
ll rqy=0;
if(x<=l&&r<=y) return tree[pos];
ll mid=(l+r)>>1;
down(pos,l,r);
if(x<=mid) rqy+=ans(x,y,l,mid,ls(pos));
if(y>mid) rqy+=ans(x,y,mid+1,r,rs(pos));
return rqy;
}
int main()
{
ios::sync_with_stdio(false);
ll n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
for(int i=1;i<=m;i++)
{
int flag; cin>>flag;
if(flag==1)
{
ll x,y,k; cin>>x>>y>>k;
update(x,y,1,n,1,k);
continue;
}
else
{
ll x,y; cin>>x>>y;
cout<<ans(x,y,1,n,1)<<endl;
}
}
return 0;
}
码风整改版
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lc(x) x<<1
#define rc(x) x<<1|1
#define mid ((l+r)>>1)
#define ll long long
using namespace std;
ll tree[100000<<2],a[100001],tag1[100000<<2],tag2[100000<<2];
inline void up(int p){
tree[p]=tree[lc(p)]+tree[rc(p)];
}
void build(ll l,ll r,ll p){
if(l==r){
tree[p]=a[l];
return ;
}
build(l,mid,lc(p));
build(mid+1,r,rc(p));
up(p);
}
void down1(ll l,ll r,ll p){
tag1[lc(p)]+=tag1[p]; tree[lc(p)]+=(mid-l+1)*tag1[p];
tag1[rc(p)]+=tag1[p]; tree[rc(p)]+=(r-mid)*tag1[p];
tag1[p]=0;
}
void update1(ll l,ll r,ll p,ll nl,ll nr,ll k){
if(l>=nl&&r<=nr){
tree[p]+=(r-l+1)*k; tag1[p]+=k;
return ;
}
down1(l,r,p);
if(nl<=mid)
update1(l,mid,lc(p),nl,nr,k);
if(nr>mid)
update1(mid+1,r,rc(p),nl,nr,k);
up(p);
}
ll ans(ll l,ll r,ll p,ll nl,ll nr){
ll zzz=0;
if(l>=nl&&r<=nr) return tree[p];
down1(l,r,p);
if(nl<=mid)
zzz+=ans(l,mid,lc(p),nl,nr);
if(nr>mid)
zzz+=ans(mid+1,r,rc(p),nl,nr);
return zzz;
}
ll read(){
ll f=1,k=0; char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar())
k=(k<<3)+(k<<1)+c-48;
return k*f;
}
int main(){
ll n=read(),m=read();
for(ll i=1;i<=n;i++) a[i]=read();
build(1,n,1);
for(ll i=1;i<=m;i++){
ll t=read();
if(t==1){
ll x=read(),y=read(),k=read();
update1(1,n,1,x,y,k);
}
else{
ll x=read(),y=read();
printf("%lld\n",ans(1,n,1,x,y));
}
}
return 0;
}
线段树2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lc(x) x<<1
#define rc(x) x<<1|1
#define mid ((l+r)>>1)
#define ll long long
using namespace std;
ll tree[100000<<2],a[100001],tag1[100000<<2],tag2[100000<<2],b;
inline void up(ll p){
tree[p]=(tree[lc(p)]%b+tree[rc(p)]%b)%b;
}
void build(ll l,ll r,ll p){
tag2[p]=1;
if(l==r){
tree[p]=a[l];
return ;
}
build(l,mid,lc(p));
build(mid+1,r,rc(p));
up(p);
}
//惊天地泣鬼神的pushdown
void down(ll l,ll r,ll p){
tree[lc(p)]=(tree[lc(p)]*tag2[p]+(mid-l+1)*tag1[p])%b;
tree[rc(p)]=(tree[rc(p)]*tag2[p]+(r-mid)*tag1[p])%b;
tag2[lc(p)]=(tag2[lc(p)]*tag2[p])%b;
tag2[rc(p)]=(tag2[rc(p)]*tag2[p])%b;
tag1[lc(p)]=(tag1[lc(p)]*tag2[p]+tag1[p])%b;
tag1[rc(p)]=(tag1[rc(p)]*tag2[p]+tag1[p])%b;
tag1[p]=0;
tag2[p]=1;
}
void update2(ll l,ll r,ll p,ll nl,ll nr,ll k){
if(l>=nl&&r<=nr){
tree[p]=(tree[p]*k)%b; tag2[p]=(tag2[p]*k)%b;
tag1[p]=(tag1[p]*k)%b;
return ;
}
down(l,r,p);
if(nl<=mid)
update2(l,mid,lc(p),nl,nr,k);
if(nr>mid)
update2(mid+1,r,rc(p),nl,nr,k);
up(p);
}
void update1(ll l,ll r,ll p,ll nl,ll nr,ll k){
if(l>=nl&&r<=nr){
tree[p]=(tree[p]+(r-l+1)*k)%b; tag1[p]=(tag1[p]+k)%b;
return ;
}
down(l,r,p);
if(nl<=mid)
update1(l,mid,lc(p),nl,nr,k);
if(nr>mid)
update1(mid+1,r,rc(p),nl,nr,k);
up(p);
}
ll ans(ll l,ll r,ll p,ll nl,ll nr){
ll zzz=0;
if(l>=nl&&r<=nr) return tree[p];
down(l,r,p);
if(nl<=mid)
zzz=(zzz+ans(l,mid,lc(p),nl,nr))%b;
if(nr>mid)
zzz=(zzz+ans(mid+1,r,rc(p),nl,nr))%b;
return zzz%b;
}
ll read(){
ll f=1,k=0; char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar())
k=(k<<3)+(k<<1)+c-48;
return k*f;
}
int main(){
ll n=read(),m=read(); b=read();
for(ll i=1;i<=n;i++) a[i]=read();
build(1,n,1);
for(ll i=1;i<=m;i++){
ll t=read();
if(t==1){
ll x=read(),y=read(),k=read();
update2(1,n,1,x,y,k);
}
if(t==2){
ll x=read(),y=read(),k=read();
update1(1,n,1,x,y,k);
}
if(t==3){
ll x=read(),y=read();
cout<<(ans(1,n,1,x,y))%b<<endl;
}
}
return 0;
}
2018/6/9更新:
线段树维护最长连续子段
每个节点存三个量:\(l、r、m\),\(l\)表示以该节点所存的左端点为起始点的最长连续子段,\(r\)表示以该节点所存的右端点为终止点的最长连续子段,\(m\)表示被该节点包含的最长连续子段。这样,我们就能通过拼接小区间来得知大区间的最长连续子段了。
inline void up(int l,int r,int p){
tree[p].l=tree[ls(p)].l; //大区间的左端点开始的连续子段最小是左区间里以左区间左端点开头的连续子段
if(tag[mid]!=tag[mid+1]&&mid-l+1==tree[ls(p)].l) //如果左区间整个都是连读的
tree[p].l+=tree[rs(p)].l; //再将右区间的以右区间开头的连续子段长度加上,下同
tree[p].r=tree[rs(p)].r;
if(tag[mid]!=tag[mid+1]&&r-mid==tree[rs(p)].r)
tree[p].r+=tree[ls(p)].r;
tree[p].m=max(tree[ls(p)].m,tree[rs(p)].m);
if(tag[mid]!=tag[mid+1])
tree[p].m=max(tree[p].m,tree[ls(p)].r+tree[rs(p)].l); 左右区间拼接
}
我们也可以用这种方法来维护最大子段和
裸题,不多BB
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ls(x) x<<1
#define rs(x) x<<1|1
#define mid ((l+r)>>1)
using namespace std;
struct zzz{
int l,r,m;
}tree[20001<<2];
bool tag[20001];
inline void up(int l,int r,int p){
tree[p].l=tree[ls(p)].l;
if(tag[mid]!=tag[mid+1]&&mid-l+1==tree[ls(p)].l)
tree[p].l+=tree[rs(p)].l;
tree[p].r=tree[rs(p)].r;
if(tag[mid]!=tag[mid+1]&&r-mid==tree[rs(p)].r)
tree[p].r+=tree[ls(p)].r;
tree[p].m=max(tree[ls(p)].m,tree[rs(p)].m);
if(tag[mid]!=tag[mid+1])
tree[p].m=max(tree[p].m,tree[ls(p)].r+tree[rs(p)].l);
}
void build(int l,int r,int p){
if(l==r){
tree[p].l=1; tree[p].r=1;
tree[p].m=1;
return ;
}
build(l,mid,ls(p));
build(mid+1,r,rs(p));
up(l,r,p);
}
void update(int l,int r,int p,int nn){
if(l==r&&l==nn){
tag[l]^=1;
return ;
}
if(nn<=mid) update(l,mid,ls(p),nn);
if(nn>mid) update(mid+1,r,rs(p),nn);
up(l,r,p);
}
int read(){
int k=0; char c=getchar();
for(;c<'0'||c>'9';) c=getchar();
for(;c>='0'&&c<='9';c=getchar())
k=(k<<3)+(k<<1)+c-48;
return k;
}
int main(){
int n=read(),m=read();
build(1,n,1);
for(int i=1;i<=m;i++){
int x=read();
update(1,n,1,x);
printf("%d\n",max(tree[1].l,max(tree[1].m,tree[1].r)));
}
return 0;
}