P3368 【模板】树状数组 2 线段树实现

解题思路

这道题目要求实现一个数据结构,支持两种操作:

  1. 区间更新:将区间[x,y]内的每个数加上一个值k

  2. 单点查询:查询某个位置x的值

这是一个典型的区间更新与单点查询问题。题目提供的代码使用了带有懒标记的线段树解法。

方法选择

线段树解法具有以下特点:

  1. 预处理时间:O(n)构建线段树

  2. 区间更新:O(logn)时间(使用懒标记)

  3. 单点查询:O(logn)时间

  4. 空间复杂度:O(n)

对于n和m都是5e5的数据规模,线段树的O(n + mlogn)复杂度完全能够胜任。

其他可能的解法

  1. 差分数组

    • 区间更新O(1)

    • 单点查询O(n)(可通过前缀和优化为O(1))

    • 空间复杂度O(n)

    • 是本题的更优选择(题目名称也提示了这点)

  2. 树状数组

    • 可以实现区间更新和单点查询

    • 但实现起来不如差分数组直观

代码注释

#include<bits/stdc++.h>
#define lc rt << 1      // 左子节点索引
#define rc rt << 1 | 1  // 右子节点索引
#define lson lc,l,mid   // 左子树参数
#define rson rc,mid + 1,r // 右子树参数
#define ll long long 
using namespace std;

const int N = 2e6 + 10, inf = 0x3f3f3f3f;

// 线段树节点结构体
struct node{
    ll sum;  // 存储区间和
    ll lazy; // 懒标记,用于延迟更新
};

node t[N << 2];  // 线段树数组,大小是原数组的4倍
int n, m;        // n-数列长度,m-操作数量
int a[N];        // 原始数列

// 更新父节点的区间和
void pushup(int rt)
{
    t[rt].sum = t[lc].sum + t[rc].sum;  // 父节点和等于左右子节点和相加
}

// 下放懒标记
void down(int rt,int l,int r,ll num)
{
    t[rt].sum += (r - l + 1) * num; // 更新区间和
    t[rt].lazy += num; // 累加懒标记
}

// 下推懒标记到子节点
void pushdown(int rt,int l,int r)
{
    if(t[rt].lazy == 0) return; // 没有懒标记则返回
    int mid = (l + r) / 2;
    down(lson,t[rt].lazy); // 下放到左子树
    down(rson,t[rt].lazy); // 下放到右子树
    t[rt].lazy = 0; // 清空当前节点的懒标记
}

// 构建线段树
void build(int rt, int l, int r)
{
    t[rt].lazy = 0; // 初始化懒标记
    if(l == r) {  // 叶子节点
        t[rt].sum = a[l];  // 存储单个元素值
        return;
    }
    int mid = (l + r) / 2;  // 计算中点
    build(lson);  // 构建左子树
    build(rson);  // 构建右子树
    pushup(rt);   // 更新当前节点的区间和
}

// 区间更新函数(将[x,y]区间的值增加z)
void change(int rt, int l, int r, int x, int y, int z)
{
    if(r < x || y < l) return;  // 超出修改范围
    if(x <= l && r <= y){  // 当前区间完全包含在更新区间内
        t[rt].sum += (r - l + 1) * z;  // 更新区间和
        t[rt].lazy += z; // 设置懒标记
        return;
    }
    pushdown(rt,l,r); // 下放懒标记
    int mid = (l + r) / 2;
    change(lson, x, y,z);  // 更新左子树
    change(rson, x, y,z);  // 更新右子树
    pushup(rt);          // 更新父节点的区间和
}

// 区间查询函数(查询[x,y]区间和)
ll query(int rt, int l, int r, int x, int y)
{
    if(r < x || y < l) return 0;  // 区间无交集返回0
    if(x <= l && r <= y) return t[rt].sum;  // 完全包含直接返回区间和
    pushdown(rt,l,r); // 下放懒标记
    int mid = (l + r) / 2;
    // 返回左右子树查询结果的和
    return query(lson,x,y) + query(rson,x,y);
}

int main()
{
    cin >> n >> m;  // 读取数列长度和操作数量
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);  // 读取初始数列
    
    build(1, 1, n);  // 构建线段树
    
    while(m--)  // 处理每个操作
    {
        int op, x, y, z;  // 操作类型和参数
        scanf("%d%d", &op, &x);  // 读取操作
        
        if(op == 2){  // 查询操作(单点查询)
            printf("%lld\n", query(1, 1, n, x, x));
        }
        else if(op == 1)  // 更新操作(区间更新)
        {
            scanf("%d%d",&y,&z);
            change(1, 1, n, x, y, z);  // 执行区间更新
        }
    }
    return 0;
}

 

posted @ 2025-05-21 10:00  CRt0729  阅读(18)  评论(0)    收藏  举报