线段树(笔记)

原理:

线段树是一颗平衡二叉树,它的节点储存一个线段以及其它的一些信息,如图所示。在这里插入图片描述

树的存储:

树的存储采用堆式存法,即存在数组里,对于节点u来说:

u << 1 是u的左儿子
u << 1 | 1是u的右儿子
u >> 1 是u的父亲

操作一,pushup操作

pushup操作是由子节点计算父节点的操作,比如父节点区间内的最大值可以由左右儿子的最大值取max

void pushup(int u){
    tr[u].maxx = tr[u << 1].maxx + tr[u << 1 | 1].maxx;
}
操作二,build操作

build函数用于建造线段树,采用递归的方法,把每一个节点线段的范围以及一些初始值赋给线段树。

void build(int u,int l,int r){//u,l,r分别代表当前节点编号,和当前区间的范围
    if(l == r)tr[u] = {l,r,w[l],0};//如果是叶节点就直接赋值
    else{
        tr[u].r = r,tr[u].l = l;//非叶节点则先赋区间值
        ll mid = l + r >> 1;//取区间中点
        build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);//递归构建左右儿子
        pushup(u);//pushup操作计算父节点其余的信息
    }
}
操作三,modify操作

modify就是用于修改线段树的某一个区间或点值。

void modify(int u,int x,int v){
    if(Tree[u].l == x && Tree[u].r == x)Tree[u].v = v;
    //如果当前修改的点是x就直接修改
    else{
        int mid = Tree[u].r + Tree[u].l >> 1;
        if(x <= mid)
            modify(u << 1,x,v);//如果x在左区间就递归左区间
        else
            modify(u << 1 | 1,x,v);//否则递归右区间
        push_up(u);//更新一下当前区间
    }
}
操作四,query操作

query用于查询某一段区间的信息,下面为求某一区间最大值的例子。

int query(int u,int l,int r){
    if(Tree[u].l >= l && Tree[u].r <= r)return Tree[u].v;
     //当前的区间被包含就直接返回信息
    int v = 0,mid = Tree[u].l + Tree[u].r >> 1;
    if(l <= mid)
        v = query(u << 1,l,r);//如果左孩子有交集就算一下左孩子
    if(r > mid)
        v = max(v,query(u << 1 | 1,l,r));//右孩子也同理
    return v;
}

例题

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
struct node
{
    ll l,r,v;
}Tree[maxn];
void push_up(int u){
    Tree[u].v = max(Tree[u << 1].v,Tree[u << 1 | 1].v);
}

void build(int u,int l,int r){
    Tree[u].l = l,Tree[u].r = r;
    if(l == r)return;
    int mid = l + r >> 1;
    build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
}

int query(int u,int l,int r){
    if(Tree[u].l >= l && Tree[u].r <= r)return Tree[u].v;
    int v = 0,mid = Tree[u].l + Tree[u].r >> 1;
    if(l <= mid)
        v = query(u << 1,l,r);
    if(r > mid)
        v = max(v,query(u << 1 | 1,l,r));
    return v;
}
void modify(int u,int x,int v){
    if(Tree[u].l == x && Tree[u].r == x)Tree[u].v = v;
    else{
        int mid = Tree[u].r + Tree[u].l >> 1;
        if(x <= mid)
            modify(u << 1,x,v);
        else
            modify(u << 1 | 1,x,v);
        push_up(u);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int m,p,lst = 0,n = 1,a,b;
    char str[3];
    cin >> m >> p;
    build(1,1,m);
    while(m--){
        cin >> str;
        if(str[0] == 'Q'){
            cin >> a;
            lst = query(1,n - a,n - 1);
            cout << lst << endl;
        }else{
            cin >> a;
            modify(1,n,(lst + a) % p);
            n++;
        }
    }
    return 0;
}

例题

操作五,pushdown操作

pushdown操作是和pushup操作截然不同的操作,他是由父节点计算子节点,与线段树的区间和懒标记有关。

void pushdown(int u){
    node &lt = tr[u << 1],&rt = tr[u << 1 | 1];
    if(tr[u].add){
        lt.add += tr[u].add;
        rt.add += tr[u].add;
        lt.sum += tr[u].add * (lt.r - lt.l + 1);
        rt.sum += tr[u].add * (rt.r - rt.l + 1);
        tr[u].add = 0;
    }
}
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define eps 1e-8
#define pb push_back
#define endl '\n'
using namespace std;
const ll maxn = 2e6 + 5;
ll n,m,w[maxn],a,b,c;
char str[maxn];
struct node
{
    ll l,r,sum,add;
}tr[maxn];
void pushup(int u){
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u){
    node &lt = tr[u << 1],&rt = tr[u << 1 | 1];
    if(tr[u].add){
        lt.add += tr[u].add;
        rt.add += tr[u].add;
        lt.sum += tr[u].add * (lt.r - lt.l + 1);
        rt.sum += tr[u].add * (rt.r - rt.l + 1);
        tr[u].add = 0;
    }
}
void build(int u,int l,int r){
    if(l == r)tr[u] = {l,r,w[l],0};
    else{
        tr[u].r = r,tr[u].l = l;
        ll mid = l + r >> 1;
        build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
        pushup(u);
    }
}
void modify(int u,int l,int r,int x){
    if(tr[u].l >= l && tr[u].r <= r){
        tr[u].sum += x * (tr[u].r - tr[u].l + 1);
        tr[u].add += x;
    }else{
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid)modify(u << 1,l,r,x);
        if(r > mid)modify(u << 1 | 1,l,r,x);
        pushup(u);
    }
}
ll query(int u,int l,int r){
    if(tr[u].l >= l && tr[u].r <= r)return tr[u].sum;
    else{
        pushdown(u);
        ll mid = tr[u].r + tr[u].l >> 1;
        ll sum = 0;
        if(l <= mid)sum = query(u << 1,l,r);
        if(r > mid)sum += query(u << 1 | 1,l,r);
        return sum;
    }
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> w[i];
    build(1,1,n);
    while(m--){
        cin >> str;
        if(str[0] == 'Q'){
            cin >> a >> b;
            cout << query(1,a,b) << endl;
        }else{
            cin >> a >> b >> c;
            modify(1,a,b,c);
        }
    }
    return 0;
}
懒标记:

懒标记比较重要,在修改区间时,我们仿照查询操作,如果当前区间已经被要修改的区间包括时,我们就停止继续修改,而是在当前区间打上标记,这个标记意味着在下次查询或者修改时需要进行pushdown操作,ps:pushdown操作通常放在最前面,懒标记可以叠加不可以替换

易错的地方:

就个人来说,线段树经常在build函数和query出错build经常忘记赋值区间,query函数经常范围查找错,一旦发生错误就会re,线段树的数组通常要开四倍的数组

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值