线段树模板一“洛谷一题”
洛谷题目链接戳这里
理解一整天学习了这个区间线段树,这个题很卡数据大小,因为开始用int类型写了所有,后来分不清了把很多位置改成long long,接下来给出ac代码:
#include <bits/stdc++.h>
using namespace std;
#define lt d<<1//左子树标签
#define rt d<<1|1//右子树标签
#define T tree[d]
const int MAXS=100100;
long long a[MAXS];
struct pre {
long long Val,add;
int l,r;
}tree[MAXS*4];
void build(int d,int l,int r){
T.l=l;
T.r=r;
if(l==r){
tree[d].Val=a[l];
return;
}
build(lt,l,(l+r)>>1);
build(rt,((l+r)>>1)+1,r);
T.Val=tree[lt].Val+tree[rt].Val;
}
void spread(int d){
if(T.add){
tree[lt].add+=T.add;
tree[rt].add+=T.add;
tree[lt].Val+=T.add*(tree[lt].r-tree[lt].l+1);
tree[rt].Val+=T.add*(tree[rt].r-tree[rt].l+1);
T.add=0;
}
}
void update(int d,int l,int r,long long k){
if(l<=T.l&&T.r<=r){
T.add+=k;
T.Val+=1LL*k*(T.r-T.l+1);
return;
}
spread(d);
int mid=(T.l+T.r)>>1;
if(l<=mid)update(lt,l,r,k);
if(r>=mid+1)update(rt,l,r,k);
T.Val=tree[lt].Val+tree[rt].Val;
}
long long ask(int d,int l,int r){
if(l<=T.l&&r>=T.r)return 1LL*T.Val;
spread(d);
int mid=(T.l+T.r)>>1;
long long SUM=0;
if(l<=mid)SUM+=ask(lt,l,r);
if(r>=mid+1)SUM+=ask(rt,l,r);
return SUM;
}
int main() {
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
build(1,1,n);
while (m--){
int op;
scanf("%d",&op);
if(op==1){
int l,r;
long long k;
scanf("%d %d %lld",&l,&r,&k);
update(1,l,r,k);
}else{
int l,r;
scanf("%d %d",&l,&r);
printf("%lld\n",ask(1,l,r));
}
}
return 0;
}
首先构建一个线段树,分为左右两极限位置,区间和和懒标签(记账本);
struct pre {
long long Val,add;
int l,r;
}tree[MAXS*4];//由于给出的数据量最大是100000,其中线段树的构造需要100000*4的最大内存(需要将非叶节点存储起来)
之后就是构建线段树了,根据给的最大值来确定树的大小。
void build(int d,int l,int r){
T.l=l;
T.r=r;
if(l==r){
//当这个树的左右边界相等时,即为叶节点时,给其赋值。
tree[d].Val=a[l];
return;
}
//从左向右构建线段树;
build(lt,l,(l+r)>>1);
build(rt,((l+r)>>1)+1,r);
//当运行到这时,以d为标签的根节点及以下的线段树就构建完成了,之后向上更新其中的区间和
T.Val=tree[lt].Val+tree[rt].Val;
}
构建完线段树之后就要为之后的修改做准备了,这时就要向下更新懒标记(记账本);懒标记的作用是保存下面所有子节点所少修改的值,由于有时候不用修改的更加细致,就用懒标记来记住下面缺少的数量,其中这个值指的是单个叶子节点,所以在更新区间和时要乘区间大小
void spread(int d){
//如果为0,则无需更新
if(T.add){
//更新下面子节点的值,向这个节点的子节点转移这个懒标记,最后更新此节点为0。
tree[lt].add+=T.add;
tree[rt].add+=T.add;
tree[lt].Val+=T.add*(tree[lt].r-tree[lt].l+1);
tree[rt].Val+=T.add*(tree[rt].r-tree[rt].l+1);
T.add=0;
}
}
做好准备之后就是修改了,当当前标签所在的区间被修改的区间覆盖时,利用懒标签来减少运行时间,当这个标签的子节点要被用到时,我们就需要更新懒标记来完善修改。
void update(int d,int l,int r,long long k){
//这里的l和r只起到传递要修改的区间。
if(l<=T.l&&T.r<=r){
T.add+=k;
T.Val+=1LL*k*(T.r-T.l+1);
return;
}
spread(d);
int mid=(T.l+T.r)>>1;
//如果左右子节点和修改区间有交集时,递归更新左右字节点
if(l<=mid)update(lt,l,r,k);
if(r>=mid+1)update(rt,l,r,k);
//下面的子节点更新完成后,向上更新
T.Val=tree[lt].Val+tree[rt].Val;
}
最后一个功能”查询“,当此处的区间被查询的区间所覆盖时返回此区间的结果。
long long ask(int d,int l,int r){
if(l<=T.l&&r>=T.r)return 1LL*T.Val;
//到此处还没有结束的话,说明子节点要运用,则更新懒标记
spread(d);
int mid=(T.l+T.r)>>1;
long long SUM=0;
if(l<=mid)SUM+=ask(lt,l,r);
if(r>=mid+1)SUM+=ask(rt,l,r);
return SUM;
}
至此这个题目所需要的功能就已经全部实现了,这个数实则用了结构体数组来存储很多的数据来分析。