线段树

线段树对编号连续的点构成的线段进行修改和统计操作,必须要求复合的是区间加法。

所谓区间加法有这样几个例子:
总数字之和 == 左区间之和 + 右区间之和
最大值 == max(左区间最大值,右区间最大值)
最大公因数 == GCD(左区间最大公因数,右区间最大公因数)
…………

线段树的操作主要由“点值的修改”、“区间的查询”、“区间的修改”、“上推下放函数”。


定义部分:

#define Max 10005

int sum[Max * 4];       //区间和存储数组
int mark[Max * 4];      //mark标记数组,表示区间的mark标记
int ar[Max];        //用来存原数组的下标

上推下推函数:

//上推
void pushup(int pn){
    sum[pn] = sum[pn * 2] + sum[pn*2 + 1];    //更新节点信息求和
}

//下推
void pushdown(int lsn, int rsn, int pn){      //lsn, rsn是左右子树的数字个数,pn是当前节点编号
    if(mark[pn]){
        mark[pn * 2] += mark[pn];
        mark[pn*2 + 1] += mark[pn];
        sum[pn * 2] += mark[pn * 2] * lsn;
        sum[pn*2 + 1] += mark[pn*2 + 1] * rsn;
        mark[pn] = 0;
    }
}

建树:

//建树
void build(int l, int r, int pn){      //[l, r]表示当前操作区间,pn表示当前操作的节点编号
    if(l == r){     //如果到达叶节点就储存数组值
        sum[l] = ar[l];
        return ;
    }
    int m = (l + r) / 2;
    build(l, m, pn * 2);        //左建树
    build(m + 1, r, pn*2 + 1);      //右建树
    pushup(pn);     //全都更新求和
}

节点值修改:

//修改节点的值
void poi_update(int pos, int num, int l, int r, int pn){      //pos代表要修改的那个节点的位置,num是要加上的数,[l, r]是当前操作区间,pn是当前节点编号
    if(l == r){
        sum[l] += num;
        return ;
    }
    int m = (l + r) / 2;
    if(pos <= m){
        poi_update(pos, num, l, m, pn * 2);
    }
    else{
        poi_update(pos, num, m + 1, r, pn*2 + 1);
    }
    pushup(pn);
}

区间修改:

//区间的修改
void edge_update(int L, int R, int num, int l, int r, int pn){      //[L, R]表示操作区间,[l, r]表示当前操作区间,num是要改变的值,pn是当前位置
    if(L <= l && r <= R){       //如果在操作区间内就改变,并且留下mark标记
        sum[pn] += num * (l - r + 1);
        mark[pn] += num;
        return ;
    }
    int m = (l + r) / 2;
    //pushdown(m - l + 1, r - m, pn);
    if(L <= m){
        edge_update(L, R, num, l, m, pn * 2);
    }
    if(m < R){
        edge_update(L, R, num, m + 1, r, pn*2 + 1);
    }
    pushup(pn);
}

区间查找:

//查找
int srch(int L, int R, int l, int r, int pn){       //L, R表示操作区间,l, r表示当前区间,pn表示当前位置
    if(L <= l && r <= R){
        return sum[pn];     //在操作区间内的就返回
    }
    int m = (l + r) / 2;
    pushdown(m - l + 1, r - m, pn);     //下放
    int ans = 0;
    if(L <= m){
        ans += srch(L, R, l, m, pn * 2);
    }
    if(R > m){
        ans += srch(L, R, m + 1, r, pn*2 + 1);
    }
    return ans;
}

完整一般数组代码:

#include <cstdio>
#include <iostream>
#define Max 10005
using namespace std;

int sum[Max * 4];       //区间和存储数组
int mark[Max * 4];      //mark标记数组,表示区间的mark标记
int ar[Max];        //用来存原数组的下标

//上推
void pushup(int pn){
    sum[pn] = sum[pn * 2] + sum[pn*2 + 1];    //更新节点信息求和
}

//下推
void pushdown(int lsn, int rsn, int pn){      //lsn, rsn是左右子树的数字个数,pn是当前节点编号
    if(mark[pn]){
        mark[pn * 2] += mark[pn];
        mark[pn*2 + 1] += mark[pn];
        sum[pn * 2] += mark[pn * 2] * lsn;
        sum[pn*2 + 1] += mark[pn*2 + 1] * rsn;
        mark[pn] = 0;
    }
}

//建树
void build(int l, int r, int pn){      //[l, r]表示当前操作区间,pn表示当前操作的节点编号
    if(l == r){     //如果到达叶节点就储存数组值
        sum[l] = ar[l];
        return ;
    }
    int m = (l + r) / 2;
    build(l, m, pn * 2);        //左建树
    build(m + 1, r, pn*2 + 1);      //右建树
    pushup(pn);     //全都更新求和
}

//修改节点的值
void poi_update(int pos, int num, int l, int r, int pn){      //pos代表要修改的那个节点的位置,num是要加上的数,[l, r]是当前操作区间,pn是当前节点编号
    if(l == r){
        sum[l] += num;
        return ;
    }
    int m = (l + r) / 2;
    if(pos <= m){
        poi_update(pos, num, l, m, pn * 2);
    }
    else{
        poi_update(pos, num, m + 1, r, pn*2 + 1);
    }
    pushup(pn);
}

//区间的修改
void edge_update(int L, int R, int num, int l, int r, int pn){      //[L, R]表示操作区间,[l, r]表示当前操作区间,num是要改变的值,pn是当前位置
    if(L <= l && r <= R){       //如果在操作区间内就改变,并且留下mark标记
        sum[pn] += num * (l - r + 1);
        mark[pn] += num;
        return ;
    }
    int m = (l + r) / 2;
    //pushdown(m - l + 1, r - m, pn);
    if(L <= m){
        edge_update(L, R, num, l, m, pn * 2);
    }
    if(m < R){
        edge_update(L, R, num, m + 1, r, pn*2 + 1);
    }
    pushup(pn);
}

//查找
int srch(int L, int R, int l, int r, int pn){       //L, R表示操作区间,l, r表示当前区间,pn表示当前位置
    if(L <= l && r <= R){
        return sum[pn];     //在操作区间内的就返回
    }
    int m = (l + r) / 2;
    pushdown(m - l + 1, r - m, pn);     //下放
    int ans = 0;
    if(L <= m){
        ans += srch(L, R, l, m, pn * 2);
    }
    if(R > m){
        ans += srch(L, R, m + 1, r, pn*2 + 1);
    }
    return ans;
}

改成结构体数组之后,除了建树的时候要写上左右区间是为了确保建树完成,其他的操作就不需要左右区间了,直接使用节点位置编号pn就可以完成后续操作。


结构体数组的线段树代码:

#include <cstdio>
#include <iostream>
#define Max 10005
using namespace std;

struct tree{        //l, r表示左右区间端点,val表示这个区间的值,mark是标记
    int l;
    int r;
    int val;
    int mark = 0;
}sum[Max * 4];
int ar[Max];        //用来存原数组的下标

//上推
void pushup(int pn){
    sum[pn].val = sum[pn * 2].val + sum[pn*2 + 1].val;    //更新节点信息求和
}

//下推
void pushdown(int lsn, int rsn, int pn){      //lsn, rsn是左右子树的数字个数,pn是当前节点编号
    if(sum[pn].mark){
        sum[pn * 2].mark += sum[pn].mark;
        sum[pn*2 + 1].mark += sum[pn].mark;
        sum[pn * 2].val += sum[pn * 2].mark * lsn;
        sum[pn*2 + 1].val += sum[pn*2 + 1].mark * rsn;
        sum[pn].mark = 0;
    }
}

//建树
void build(int l, int r, int pn){      //[l, r]表示当前操作区间,pn表示当前操作的节点编号
    sum[pn].l = l;
    sum[pn].r = r;
    if(sum[pn].l == sum[pn].r){     //如果到达叶节点就储存数组值
        sum[pn].val = ar[l];
        return ;
    }
    int m = (l + r) / 2;
    build(l, m, pn * 2);        //左建树
    build(m + 1, r, pn*2 + 1);      //右建树
    pushup(pn);     //全都更新求和
}

//修改节点的值
void poi_update(int pos, int num, int pn){      //pos代表要修改的那个节点的位置,num是要加上的数,pn是当前节点编号
    if(sum[pn].l == sum[pn].r){
        sum[pn].val += num;
        return ;
    }
    int m = (sum[pn].l + sum[pn].r) / 2;
    if(pos <= m){
        poi_update(pos, num, pn * 2);
    }
    else{
        poi_update(pos, num, pn*2 + 1);
    }
    pushup(pn);
}

//区间的修改
void edge_update(int L, int R, int num, int pn){      //[L, R]表示操作区间,num是要改变的值,pn是当前位置
    if(L <= sum[pn].l && sum[pn].r <= R){       //如果在操作区间内就改变,并且留下mark标记
        sum[pn].val += num * (sum[pn].r - sum[pn].l + 1);
        sum[pn].mark += num;
        return ;
    }
    int m = (sum[pn].l + sum[pn].r) / 2;
    //pushdown(m - sum[pn].l + 1, sum[pn].r - m, pn);
    if(L <= m){
        edge_update(L, R, num, pn * 2);
    }
    if(m < R){
        edge_update(L, R, num, pn*2 + 1);
    }
    pushup(pn);
}

//查找
int srch(int L, int R, int pn){       //L, R表示操作区间,pn表示当前位置
    if(L <= sum[pn].l && sum[pn].r <= R){
        return sum[pn].val;     //在操作区间内的就返回
    }
    int m = (sum[pn].l + sum[pn].r) / 2;
    pushdown(m - sum[pn].l + 1, sum[pn].r - m, pn);     //下放
    int ans = 0;
    if(L <= m){
        ans += srch(L, R, pn * 2);
    }
    if(R > m){
        ans += srch(L, R, pn*2 + 1);
    }
    return ans;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值