线段树模板一“洛谷一题”

洛谷题目链接戳这里
理解一整天学习了这个区间线段树,这个题很卡数据大小,因为开始用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;
}

至此这个题目所需要的功能就已经全部实现了,这个数实则用了结构体数组来存储很多的数据来分析。

posted on 2025-04-07 20:21  神奇猫猫侠  阅读(17)  评论(0)    收藏  举报

导航