线段树——hyl天梦
1简介
线段树是一种比较实用的数据结构,支持区间修改,区间查询,单点修改,单点查询,维护区间最值。
2讲解
2.1基本值
(注:本博客只是设计线段树区间求和)
在结构体中我们维护一个区间值,区间长度,和该节点的标记
这个标记非常重要,是线段树的灵魂
代码:
struct node{
ll sum,len;
ll ad;
};
2.2建树
对于一个数组a来说,把它l到r的一个区间看做一个节点,根节点的区间是l到r,根节点两个儿子的区间分别为l到mid,和mid+1到r,其中mid=l+r>>1;由此不断往下递归,直至该区间内只剩一个节点。
上述说明不仅说明了线段树的构造原理,也可以得出线段树的建树代码:
inline void build(ll k,ll l,ll r)
{
if(l==r)
{
tree[k].len=1;
tree[k].sum=a[l];
return;
}
ll mid=l+r>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
pushup(k);
}
建树是O(n)的。
其中pushup函数是合并函数,根据代码,我们可以知道在建树中,是首先建立叶子结点,然后逐步回溯,合并,建立父节点,那么就有了合并函数。
2.3合并函数
合并函数比较好理解,一个节点对应一个区间,那么这个区间l到r我们所要维护的值一定取决于它的两个儿子,例如,如果我们所维护的是l到r的和,那么该节点所维护的l到r的区间和一定是它的两个儿子:l到mid和mid+1到r的和,长度也是如此,由此可以得出:
inline void pushup(ll k)
{
tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
tree[k].len=tree[k*2].len+tree[k*2+1].len;
}
2.4加法函数与标记下传
如果要给一个区间和为q,区间长度为len的区间进行把区间里的每个数都加x的操作,那么就是相当于整个区间的和加上x*len:
inline void A(ll k,ll x)
{
tree[k].sum+=tree[k].len*x;
tree[k].ad+=x;
}
但是我们并没有修改这个区间的每一个数,如果我们需要用到这个区间里的某些数但是又不是全部,这个时候因为我们个这个区间的标记加上了x,也就是说我们标记l到r的区间加上了x,接下来我们要做的就是让标记下传,把标记传给该节点的两个儿子,同时更新它的两个儿子所维护的区间和与标记,这样,我们需要用到什么程度就标记下传的什么程度,一边修改一边标记下传。
这就体现出了为什么线段树的时间复杂度更优:我们需要修改l到r的区间和就只看l到r的区间和,而不用给l到r的每一个值都进行修改,如果有需要用到一些更小的区间,那么标记下传
标记下传同时,不要忘了清零该节点标记:
inline void pushdown(ll k)
{
A(k*2,tree[k].ad);
A(k*2+1,tree[k].ad);
tree[k].ad=0;
}
2.5区间更改
如果让我们修改的区间已经查到了,就执行加法操作,如果没有,就看他的左右儿子,同时标记下传,最后不要忘记合并
有时我们所要修改的区间包含在多个线段树节点中,我们分治来考虑,如果mid把我们的目标区间分成两段,那就分别来处理
下面的代码中:z到y指的是目标区间,l到r指的是当前区间,k指的是当前区间所对应的线段树节点,x指的是要给区间修改的值
inline void change(ll k,ll l,ll r,ll z,ll y,ll x)
{
if(l==z&&r==y)
{
A(k,x);
return;
}
if(tree[k].ad) pushdown(k);
ll mid=l+r>>1;
if(y<=mid) change(k*2,l,mid,z,y,x);
else if(z>=mid+1) change(k*2+1,mid+1,r,z,y,x);
else change(k*2,l,mid,z,mid,x),change(k*2+1,mid+1,r,mid+1,y,x);
pushup(k);
}
2.6区间查询
区间查询的思路与2.5差不多,基本上还是一个分治的思想,同时别忘了标记下传:
inline ll ask_sum(ll k,ll l,ll r,ll z,ll y)
{
if(l==z&&r==y) return tree[k].sum;
if(tree[k].ad) pushdown(k);
ll mid=l+r>>1;
if(y<=mid) return ask_sum(k*2,l,mid,z,y);
else if(z>=mid+1) return ask_sum(k*2+1,mid+1,r,z,y);
else return ask_sum(k*2,l,mid,z,mid)+ask_sum(k*2+1,mid+1,r,mid+1,y);
}
总结
以上就是线段树大致内容,博主讲的说实话并不是很好,希望多多谅解,如果有不清楚的地方可以在评论区里提问。这里附上总代码,该代码可以过洛谷上的线段树模板1
本博客只是介绍了区间加法,实际上区间加减乘除,最值,线段树都可以维护。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 1000100
#define M number
using namespace std;
ll n,m,a[N];
struct Stree{
struct node{
ll sum,len;
ll ad;
};
node tree[N<<4];
inline void A(ll k,ll x)
{
tree[k].sum+=tree[k].len*x;
tree[k].ad+=x;
}
inline void pushdown(ll k)
{
A(k*2,tree[k].ad);
A(k*2+1,tree[k].ad);
tree[k].ad=0;
}
inline void pushup(ll k)
{
tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;
tree[k].len=tree[k*2].len+tree[k*2+1].len;
}
inline void build(ll k,ll l,ll r)
{
if(l==r)
{
tree[k].len=1;
tree[k].sum=a[l];
return;
}
ll mid=l+r>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
pushup(k);
}
inline void change(ll k,ll l,ll r,ll z,ll y,ll x)
{
if(l==z&&r==y)
{
A(k,x);
return;
}
if(tree[k].ad) pushdown(k);
ll mid=l+r>>1;
if(y<=mid) change(k*2,l,mid,z,y,x);
else if(z>=mid+1) change(k*2+1,mid+1,r,z,y,x);
else change(k*2,l,mid,z,mid,x),change(k*2+1,mid+1,r,mid+1,y,x);
pushup(k);
}
inline ll ask_sum(ll k,ll l,ll r,ll z,ll y)
{
if(l==z&&r==y) return tree[k].sum;
if(tree[k].ad) pushdown(k);
ll mid=l+r>>1;
if(y<=mid) return ask_sum(k*2,l,mid,z,y);
else if(z>=mid+1) return ask_sum(k*2+1,mid+1,r,z,y);
else return ask_sum(k*2,l,mid,z,mid)+ask_sum(k*2+1,mid+1,r,mid+1,y);
}
};
Stree stree;
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
stree.build(1,1,n);
for(int i=1;i<=m;i++)
{
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op==1)
{
ll x;
scanf("%lld",&x);
stree.change(1,1,n,l,r,x);
}
else printf("%lld\n",stree.ask_sum(1,1,n,l,r));
}
}
线段树2代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<vector>
#define N 1000010
#define ll long long
using namespace std;
ll n,m,p;
ll sum[N<<2],len[N<<2],a[N],ad_1[N<<2],ad_2[N<<2];
void pushup(ll k)
{
sum[k]=(sum[k*2]+sum[k*2+1])%p;
len[k]=len[k*2]+len[k*2+1];
}
void A(ll k,ll x)
{
sum[k]=(x*sum[k])%p;
ad_1[k]=(ad_1[k]*x)%p;
ad_2[k]=(ad_2[k]*x)%p;
if(ad_1[k]==0) ad_1[k]=p;
}
void B(ll k,ll x)
{
sum[k]=(sum[k]+(len[k]*x)%p)%p;
ad_2[k]=(ad_2[k]+x)%p;
}
void pushdown_2(ll k)
{
B(k*2,ad_2[k]);
B(k*2+1,ad_2[k]);
ad_2[k]=0;
}
void pushdown_1(ll k)
{
A(k*2,ad_1[k]);
A(k*2+1,ad_1[k]);
ad_1[k]=1;
}
void change_1(ll k,ll l,ll r,ll z,ll y,ll x)
{
if(l==z&&r==y)
{
A(k,x);
return;
}
if(ad_1[k]>1) pushdown_1(k);
if(ad_2[k]) pushdown_2(k);
ll mid=l+r>>1;
if(y<=mid) change_1(k*2,l,mid,z,y,x);
else if(mid<z) change_1(k*2+1,mid+1,r,z,y,x);
else
{
change_1(k*2,l,mid,z,mid,x);
change_1(k*2+1,mid+1,r,mid+1,y,x);
}
pushup(k);
}
int ask_sum(ll k,ll l,ll r,ll z,ll y)
{
if(l==z&&r==y)
{
return sum[k];
}
if(ad_1[k]>1) pushdown_1(k);
if(ad_2[k]) pushdown_2(k);
ll mid=l+r>>1;
if(y<=mid) return ask_sum(k*2,l,mid,z,y);
else if(mid<z) return ask_sum(k*2+1,mid+1,r,z,y);
else return (ask_sum(k*2,l,mid,z,mid)%p+ask_sum(k*2+1,mid+1,r,mid+1,y)%p)%p;
}
void change_2(ll k,ll l,ll r,ll z,ll y,ll x)
{
if(l==z&&r==y)
{
B(k,x);
return;
}
if(ad_1[k]>1) pushdown_1(k);
if(ad_2[k]) pushdown_2(k);
ll mid=l+r>>1;
if(y<=mid) change_2(k*2,l,mid,z,y,x);
else if(mid<z) change_2(k*2+1,mid+1,r,z,y,x);
else
{
change_2(k*2,l,mid,z,mid,x);
change_2(k*2+1,mid+1,r,mid+1,y,x);
}
pushup(k);
}
void build(ll k,ll l,ll r)
{
ad_1[k]=1;
if(l==r)
{
sum[k]=a[l];
len[k]=1;
return;
}
ll mid=l+r>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
pushup(k);
}
void print()
{
cout<<endl<<endl;
int i=0;
do
{
cout<<sum[++i]<<" ";
}while(sum[i]);
cout<<endl<<endl;
}
int main()
{
// freopen("he.in","r",stdin);
// freopen("he.out","w",stdout);
ios::sync_with_stdio(false);
cin>>n>>m>>p;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
//print();
while(m--)
{
int op,a,b,x;
cin>>op>>a>>b;
if(op==1)
{
cin>>x;
change_1(1,1,n,a,b,x);
//print();
}
else if(op==2)
{
cin>>x;
change_2(1,1,n,a,b,x);
//print();
}
else
{
int ans=ask_sum(1,1,n,a,b);
cout/*<<"ans="*/<<ans<<endl;
//print();
}
}
return 0;
}