直接给例题,先去看题目:洛谷之线段树模板
先给你们看不带懒标记的线段树代码(含解析)
单点修改(70分):
修改区间:O(nlongn)
查询:O(longn)
代码:
#include<bits/stdc++.h>
#define ll long long
const int maxn=1e5+5;
using namespace std;
ll n,m,x,y,k,a[maxn*4],f[maxn];
//n表示数组长度,m表示m次询问,x和y表示区间[x,y]
//k表示这个区间[x,y]均加上k
//f[i]表示原数组,a[i]表示我们的线段树
void push_up(ll root)//向上更新
{
a[root]=a[root*2]+a[root*2+1];//父节点等于左子节点+右子节点。
}
void build(ll l,ll r,ll root)
{
if(l==r)//搜到底了,长度为1的区间肯定是l=r
{
a[root]=f[l];
return;
}
ll mid=(l+r)/2;//二分搜索
build(l,mid,root*2);//先建左子节点(左树)
build(mid+1,r,root*2+1);//后建右子节点(右数)
push_up(root);//向上更新,用于更新父节点的值
}
void update(ll l,ll r,ll root,ll id)//区间更新(这个区间长度为1),位置为id
{
if(l==r)//如果l==r,说明我们已经到大id这个点了,那么就加上k
{
a[root]+=k;return ;
}
ll mid=(l+r)/2;//二分
if(mid>=id)update(l,mid,root*2,id);//如果id在左边 ,我们就去左边
else update(mid+1,r,root*2+1,id);//如果id在右边,我们就去右边
push_up(root);//修改子节点的值后,我们要向自己的父亲报告,并且更新父节点的值
}
ll qurey(ll l,ll r,ll root)//查询区间
{
ll ans=0;//用来记录这个区间的和的值
if(l>=x&&r<=y)return a[root];//如果[l,r]在我们查询的[x,y]内直接返回这个区间的值
if(l==r)return a[root];//如果l==r说明,已经找到低了,而且是一个满足[x,y]区间长度为1的一个值。
ll mid=(l+r)/2;//二分
if(mid>=x)ans+=qurey(l,mid,root*2);//如果[x,y]区间在左边有满足条件,那么我们就把左边满足条件的值都加起来
if(mid<y)ans+=qurey(mid+1,r,root*2+1);//如果[x,y]区间在右边有满足条件,那么我们就把右边满足条件的值都加起来
return ans; //最后我们返回的是[x,y]区间的和
}
int main()
{
ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>f[i];//输入原始数组
build(1,n,1);//建树
while(m--)//m次询问
{
int q;
cin>>q;//根据题意,进行操作
if(q==1)
{
cin>>x>>y>>k;
//因为我们是单点修改,因此复杂度为nlongn;
for(ll i=x;i<=y;i++)update(1,n,1,i);
}
else
{
cin>>x>>y;
//输出[x,y]的总和
cout<<qurey(1,n,1)<<endl;
}
}
return 0;
}
因为:对于 %100% 的数据:1≤n,m≤1e5
显然单点修改的操作是过不了这题的因为n*m已经超过1e8了。下面介绍的是带有懒标记的代码,其复杂度为O(longn)
如果代码看不懂没关系:先去看这个视频:线段树-懒标记
这个视频不需要看完,只需要懂得懒标记是怎么操作就行了
代码:
#include<bits/stdc++.h>
#define ll long long
#define hh 0x3f3f3f3f
const int maxn=1e5+5;
using namespace std;
ll f[maxn],a[maxn*4],sign[maxn*4],x,y,k,n,m;
//n表示数组长度,m表示m次询问,x和y表示区间[x,y]
//k表示这个区间[x,y]均加上k
//f[i]表示原数组,a[i]表示我们的线段树
//sign[i]表示存放我们的懒标记的值
void push_up(ll root)//向上更新
{
a[root]=a[root*2]+a[root*2+1];//这个子节点向自己的父节点汇报,并更新父节点的值
}
void build(ll l, ll r, ll root)// 建树
{
sign[root]=0;//让我们的标记函数初始化。
if(l==r)//搜到底了,长度为1的区间肯定是l=r
{
a[root]=f[l];return ;
}
ll mid=(l+r)/2;//二分搜索
build(l,mid,root*2);//先建左子节点(左树)
build(mid+1,r,root*2+1);//后建右子节点(右树)
push_up(root);//向上更新,用于更新父节点的值
}
void push_down(ll l,ll r,ll root)//下放标记
{
if(sign[root])//如果这个root有标记
{
ll mid=(l+r)/2;//二分
sign[root*2]+=sign[root];//把这个标记下放到它的左子节点
sign[root*2+1]+=sign[root];//把这个标记下放到它的右子节点
a[root*2]+=sign[root]*(mid-l+1);//更新左子节点的值
a[root*2+1]+=sign[root]*(r-mid);//更新 右子节点的值
sign[root]=0;//取消root层的标记
}
}
void update(ll l ,ll r ,ll root)//更新区间
{
if(l>=x&&r<=y)//如果[l,r]在[x,y]区间内,那么更新[l,r]的值
{
sign[root]+=k;//在root上加上标记
a[root]+=(r-l+1)*k;//更新[l,r]的值
return ;
}
push_down(l,r,root);//把这个标记下放到它的左右两个子节点
ll mid=(l+r)/2;//二分
if(mid>=x)update(l,mid,root*2);//如果mid左边有[x,y]的区间那么就去左边找
if(mid<y)update(mid+1,r,root*2+1);//如果mid右边有[x,y]的区间那么就去右边找
push_up(root);修改区间值后,我们要向自己的父亲报告,并且更新父节点的值
}
ll qurey(ll l,ll r,ll root)//查询区间
{
ll ans=0;//用来记录这个区间的和的值
if(l>=x&&r<=y)return a[root];//如果[l,r]在我们查询的[x,y]内直接返回这个区间的值
push_down(l,r,root);//把这个root层的标记下放到它的左右子节点
ll mid=(l+r)/2;//二分
if(mid>=x)ans+=qurey(l,mid,root*2);//如果[x,y]区间在左边有满足条件,那么我们就把左边满足条件的值都加起来
if(mid<y)ans+=qurey(mid+1,r,root*2+1);//如果[x,y]区间在右边有满足条件,那么我们就把右边满足条件的值都加起来
return ans;//最后我们返回的是[x,y]区间的和
}
int main()
{
ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>f[i];//输入原始数组
build(1,n,1);//建树
while(m--)//m次询问
{
int q;
cin>>q;//根据题意,进行操作
if(q==1)
{
cin>>x>>y>>k;
//因为我们是带有懒标记的区间,因此复杂度为longn;
update(1,n,1);
}
else
{
cin>>x>>y;
//输出[x,y]的总和
cout<<qurey(1,n,1)<<endl;
}
}
return 0;
}