线段树

本文深入探讨了段树(Segment Tree)的数据结构及其在算法竞赛中的应用,详细讲解了段树的构造、更新和查询操作,并提供了两种不同的实现方式,包括lyd和廖哥的写法,帮助读者理解如何高效地解决区间更新和查询问题。
  1. lyd写法
/*
    Name: SegmentTree
    Author: Jack
    Date: 09/04/19 19:40 
*/
#include<iostream>
#include<cstdio>
#define ls(p) p*2//左子树编号
#define rs(p) ls(p)+1//右子树编号
#define md(x,y) (x+y)>>1//平均值
using namespace std;
const int maxn = 100000 + 10;
struct Segmenttree {
    int l, r;//左边界,右边界
    long long sum, add;//区间和,延迟标记
#define l(x) tree[x].l
#define r(x) tree[x].r
#define sum(x) tree[x].sum
#define add(x) tree[x].add
} tree[maxn * 4];
int a[maxn], n, m;
void bulid(int p, int l, int r) {
    //建造树
    l(p) = l, r(p) = r;
    //左为左,右为右
    if(l == r) {
        sum(p) = a[l];
        return ;
        //如果已经分到了最小段(单节点),则其区间和为其数值
    }

    int mid = md(l,r);
    bulid(ls(p), l, mid);//建造左子树
    bulid(rs(p), mid + 1, r);//建造右子树
    sum(p) = sum(ls(p)) + sum(rs(p));//区间和为左右子树和之和
}
void spread(int p) {
    if(add(p)) {
        sum(ls(p)) += add(p) * (r(ls(p)) - l(ls(p)) + 1);
        sum(rs(p)) += add(p) * (r(rs(p)) - l(rs(p)) + 1);
        //左子树之和+=延迟标记*左子树子节点个数,右子树同理
        add(ls(p)) += add(p);
        add(rs(p)) += add(p);
        //下传一层标记
        add(p) = 0;
        //清空原标记
    }
}
void change(int p, int l, int r, int  d) {
    if(l <= l(p) && r >= r(p)) {
        //由线段树性质可得,当以上条件都满足时,[l,r]必然为一个子树
        sum(p) += (long long)d * (r(p) - l(p) + 1);//加sum
        add(p) += d;//做标记
        return ;
    }

    spread(p);
    int mid = md(l(p),r(p));
    if(l <= mid)change(ls(p), l, r, d);
    if(r > mid)change(rs(p), l, r, d);
    //将此不可直接解区间转化为可解或不可解系数低的区间
    sum(p) = sum(ls(p)) + sum(rs(p));//求和
}
long long ask(int p, int l, int r) {
    if(l <= l(p) && r >= r(p))return sum(p);
    //同change函数
    spread(p);
    int mid = md(l(p),r(p));
    long long val = 0;
    if(l <= mid)val += ask(ls(p), l, r);
    if(r > mid)val += ask(rs(p), l, r);
    //类似于change,将不可直接解区间转化为可解或不可解系数低的区间
    return val;
}
int main() {
    cin >> n >> m;

    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }

    bulid(1, 1, n);//在编号为1的子树,[1,n]区间内建造树

    while(m--) {
        int opt, l, r, d;
        scanf("%d%d%d", &opt, &l, &r);

        if(opt == 1) {
            scanf("%d", &d);
            change(1, l, r, d);
        } else
            printf("%lld\n", ask(1, l, r));
    }

    return 0;
}
  1. 廖哥写法
/*
    Name: SegmentTree
    Author: Jack
    Date: 09/04/19 19:43 
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#define ls(h) h<<1
#define rs(h) h<<1|1
const int maxn=100000+10;
using namespace std;
long long c[maxn*4],sum,lazy[maxn*4];
void init(int h,int s,int t){
    int mid=(s+t)>>1;
    if(s==t)scanf("%d",&c[h]);
    else{
        init(ls(h),s,mid);
        init(rs(h),mid+1,t);
        c[h]=c[ls(h)]+c[rs(h)];
    }
}
void pushdown(int h,int s,int t,int num){
    c[h]+=(t-s+1)*num;
    lazy[h]+=num;
}
void gx(int h,int s,int t,int L,int R,int num){
    int mid=(s+t)>>1;
    if(L<=s && t<=R){
        c[h]+=(t-s+1)*num;
        lazy[h]+=num;
    }else{
        if(lazy[h]){
            pushdown(ls(h),s,mid,lazy[h]);
            pushdown(rs(h),mid+1,t,lazy[h]);
            lazy[h]=0;
        }
        if(L<=mid)gx(ls(h),s,mid,L,R,num);
        if(mid<R)gx(rs(h),mid+1,t,L,R,num);
        c[h]=c[ls(h)]+c[rs(h)];
    }
}
void cx(int h,int s,int t,int L,int R){
    int mid=(s+t)>>1;
    if(L<=s && t<=R)sum+=c[h];
    else{
        if(lazy[h]){
            pushdown(ls(h),s,mid,lazy[h]);
            pushdown(rs(h),mid+1,t,lazy[h]);
            lazy[h]=0;
        }
        if(L<=mid)cx(ls(h),s,mid,L,R);
        if(mid<R)cx(rs(h),mid+1,t,L,R); 
    }
}
int main(){
    int i,j,k,m,n,x,y,z;
    cin>>n>>m;
    init(1,1,n);
    for(i=1;i<=m;i++){
        scanf("%d",&k);
        if(k==1){
            scanf("%d%d%d",&x,&y,&z);
            gx(1,1,n,x,y,z);
        }else{
            scanf("%d%d",&x,&y);
            sum=0;
            cx(1,1,n,x,y);
            printf("%lld\n",sum);
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/yangxuejian/p/10759267.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值