线段树的常见应用

一、单点修改 区间查询

例: 已知一个数列,有两种操作:
1、在数列末尾插入一个数
2、查询当前数列中末尾L个数中的最大值

P1198 [JSOI2008] 最大数

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 2e5 + 10;
struct segmentTree {
    int l, r;
    int v;
} tr[4 * N];

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if(l == r) return ;
    int mid = l + (r - l) / 2;
    
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}

void modify(int u, int i, int x) {
    if(tr[u].l == i && tr[u].r == i) {
        tr[u].v = x;
    } else {
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
        if(i <= mid) modify(u << 1, i, x);
        else modify(u << 1 | 1, i, x);
        
        tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
    }
}

int query(int u, int l, int r) {
    if(l <= tr[u].l && tr[u].r <= r) return tr[u].v;
    else {
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
        int Max = INT_MIN;
        
        if(l <= mid) Max = max(Max, query(u << 1, l, r));
        if(r > mid) Max = max(Max, query(u << 1 | 1, l, r));
        
        return Max;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int tt, mod, len = 0, Last = 0;
    cin >> tt >> mod;
    
    build(1, 1, tt);
    
    while(tt --) {
        char op;
        cin >> op;
        
        if(op == 'A') {
            int x;
            cin >> x;
            
            modify(1, len + 1, ((LL) Last + x) % mod);
            len ++;
        } else {
            int i;
            cin >> i;
            
            Last = query(1, len - i + 1, len);
            cout << Last << '\n';
        }
    }
    
    return 0;
}

二、区间修改、区间查询(懒标记)

懒标记的核心思想在于:

1、延迟更新

当需要对某个区间进行更新时,不立即更新所有相关节点,而是将更新信息暂时存储在懒标记中。

只有在后续查询或更新需要访问这些节点时,才将懒标记中的更新信息应用到子节点。

2、减少操作次数

通过懒标记,避免了不必要的递归更新,减少了时间复杂度。

例如,在区间更新时,原本需要更新多个节点,现在只需更新当前节点并设置懒标记,后续需要时再传递更新。

3、标记传递

当访问带有懒标记的节点时,先将标记传递给子节点,并更新子节点的值,然后清除当前节点的标记。

比如有一个操作将区间[4,5]的每个数都加上2,那懒标记add先只传递到[4,5]区间,若之后有查询操作查询区间[4,4]或区间[5,5]中的数的总和时,再将懒标记add向下传递。

例: 已知一个数列,有两种操作:
1、将某区间每一个数加上x
2、求出某区间每一个数的和

P3372 【模板】线段树 1

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 100010;
int n, a[N];

struct segmentTree {
    int l, r;
    LL sum, lazy;
} tr[N * 4];

void pushup(int u) {
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u) {
    auto& root = tr[u], & left = tr[u << 1], & right = tr[u << 1 | 1];
    
    if(root.lazy) {
        left.lazy += root.lazy;
        left.sum += (LL) (left.r - left.l + 1) * root.lazy;
        
        right.lazy += root.lazy; 
        right.sum += (LL) (right.r - right.l + 1) * root.lazy;
        
        root.lazy = 0;
    }
}

void build(int u, int l, int r) {
    if(l == r) {
        tr[u] = {l, r, a[l], 0};
    } else {
        tr[u] = {l, r};
        int mid = l + (r - l) / 2;
        
        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(l <= tr[u].l && tr[u].r <= r) {
        tr[u].sum += (LL) (tr[u].r - tr[u].l + 1) * x;
        tr[u].lazy += x;
    } else {
        pushdown(u);
        
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
        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(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    else {
        pushdown(u);
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
    
        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() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int tt;
    cin >> n >> tt;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    build(1, 1, n);
    
    while(tt --) {
        char op;
        cin >> op;
        
        if(op == '2') {
            int l, r;
            cin >> l >> r;
            
            cout << query(1, l, r) << '\n';
        } else {
            int l, r, x;
            cin >> l >> r >> x;
        
            modify(1, l, r, x);
        }
    }
    
    return 0;
}

P3373 【模板】线段树 2
该问题相比于 P3372 【模板】线段树 1 多了一个乘法操作,需要考虑乘法与加法的先后顺序

应该先乘再加,如:
tr[u << 1].sum = ((LL) tr[u << 1].sum * tr[u].mul + tr[u].add * k1) % mod
tr[u << 1].add = ((LL) tr[u << 1].add * tr[u].mul + tr[u].add) % mod

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;

int n, tt, a[N], mod;

struct segmentTree {
    int l, r;
    LL sum, add, mul;
} tr[4 * N];

void pushup(int u) {
    tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % mod;
}

void pushdown(int u) {
    int k1 = tr[u << 1].r - tr[u << 1].l + 1;
    int k2 = tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1;
    
    tr[u << 1].sum = ((LL) tr[u << 1].sum * tr[u].mul + tr[u].add * k1) % mod;
    tr[u << 1 | 1].sum = ((LL) tr[u << 1 | 1].sum * tr[u].mul + tr[u].add * k2) % mod;
    
    tr[u << 1].mul = (LL) tr[u << 1].mul * tr[u].mul % mod;
    tr[u << 1 | 1].mul = (LL) tr[u << 1 | 1].mul * tr[u].mul % mod;
    
    tr[u << 1].add = ((LL) tr[u << 1].add * tr[u].mul + tr[u].add) % mod;
    tr[u << 1 | 1].add = ((LL) tr[u << 1 | 1].add * tr[u].mul + tr[u].add) % mod;
    
    tr[u].add = 0;
    tr[u].mul = 1;
}

void build(int u, int l, int r) {
    tr[u] = {l, r};
    tr[u].sum = 0;
    tr[u].add = 0;
    tr[u].mul = 1;
    
    if(l == r) {
        tr[u].sum = a[l] % mod;
    } else {
        int mid = l + (r - l) / 2;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        
        pushup(u);
    }
}

void modify_add(int u, int l, int r, int d) {
    if(l <= tr[u].l && tr[u].r <= r) {
        tr[u].sum = (tr[u].sum + (LL) (tr[u].r - tr[u].l + 1) * d) % mod;
        tr[u].add = (tr[u].add + d) % mod;
    } else {
        pushdown(u);
        
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
        if(l <= mid) modify_add(u << 1, l, r, d);
        if(r > mid) modify_add(u << 1 | 1, l, r, d);
        
        pushup(u);    
    }
}

void modify_mul(int u, int l, int r, int v) {
    if(l <= tr[u].l && tr[u].r <= r) {
        tr[u].sum = tr[u].sum * v % mod;
        tr[u].mul = tr[u].mul * v % mod;
        tr[u].add = tr[u].add * v % mod;
    }else {
        pushdown(u);
        
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
        if(l <= mid) modify_mul(u << 1, l, r, v);
        if(r > mid) modify_mul(u << 1 | 1, l, r, v);
        
        pushup(u);    
    }
}

LL query(int u, int l, int r) {
    if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    else {
        pushdown(u);
        int mid = tr[u].l + (tr[u].r - tr[u].l) / 2;
        
        LL sum = 0;
        if(l <= mid) sum = query(u << 1, l, r) % mod;
        if(r > mid) sum = (sum + query(u << 1 | 1, l, r)) % mod;
        
        return sum;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> tt >> mod;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    build(1, 1, n);
    
    while(tt --) {
        int op, l, r, k;
        cin >> op;
        
        if(op == 1) {
            cin >> l >> r >> k;
            modify_mul(1, l, r, k);
        } else if(op == 2) {
            cin >> l >> r >> k;
            modify_add(1, l, r, k);
        } else {
            cin >> l >> r;
            cout << query(1, l, r) << '\n';
        }
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值