火龙信奥
  • 首页
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

线段树笔记 - 查询,单点修改

作者: 作者的头像   huolong , 时间:2026-02-05 16:33:10 , 所有人可见, 阅读  19

代码实现

# 定义最大数组大小(题目中 N 最大为 10^5)
N = 100010

# 原始数组 w,1-indexed(w[1] ~ w[n] 存储实际数据)
w = [0] * N

# 线段树数组:存储每个节点的区间和
tr_sum = [0] * (4 * N)  # 开 4*N 空间是线段树的经典做法,确保不会越界

# 线段树数组:存储每个节点对应的区间左端点 l
tr_l = [0] * (4 * N)

# 线段树数组:存储每个节点对应的区间右端点 r
tr_r = [0] * (4 * N)


def pushup(u):
    """
    向上更新:根据左右子节点的值,更新当前节点 u 的区间和。
    即:父节点的和 = 左子树和 + 右子树和
    """
    tr_sum[u] = tr_sum[u << 1] + tr_sum[u << 1 | 1]


def build(u, l, r):
    """
    构建线段树
    参数:
        u: 当前节点的编号(从 1 开始)
        l, r: 当前节点所代表的区间 [l, r](闭区间,1-indexed)
    """
    if l == r:
        # 叶子节点:区间只有一个元素
        tr_l[u] = l          # 记录左边界
        tr_r[u] = r          # 记录右边界
        tr_sum[u] = w[r]     # 叶子节点的和就是该位置的值(w 是 1-indexed)
    else:
        # 非叶子节点:需要递归构建左右子树
        tr_l[u] = l
        tr_r[u] = r
        mid = (l + r) // 2   # 分割区间

        # 递归构建左子树:节点编号为 u*2,区间 [l, mid]
        build(u << 1, l, mid)

        # 递归构建右子树:节点编号为 u*2+1,区间 [mid+1, r]
        build(u << 1 | 1, mid + 1, r)

        # 构建完子树后,向上更新当前节点的和
        pushup(u)


def query(u, l, r):
    """
    查询区间 [l, r] 的和(1-indexed)
    参数:
        u: 当前遍历到的线段树节点编号
        l, r: 要查询的目标区间
    返回:
        区间 [l, r] 内所有元素的和
    """
    # 如果当前节点所代表的区间 [tr_l[u], tr_r[u]] 完全包含在 [l, r] 内
    if tr_l[u] >= l and tr_r[u] <= r:
        return tr_sum[u]  # 直接返回该节点存储的和,无需继续递归

    # 否则,需要拆分到子区间
    mid = (tr_l[u] + tr_r[u]) // 2
    total = 0

    # 如果查询区间的左半部分与左子树有交集(即 l <= mid)
    if l <= mid:
        total += query(u << 1, l, r)  # 递归查询左子树

    # 如果查询区间的右半部分与右子树有交集(即 r > mid)
    if r > mid:
        total += query(u << 1 | 1, l, r)  # 递归查询右子树

    return total


def modify(u, x, v):
    """
    单点修改:将位置 x 的值增加 v(不是赋值!)
    参数:
        u: 当前节点编号
        x: 要修改的位置(1-indexed)
        v: 要增加的值
    """
    if tr_l[u] == tr_r[u]:
        # 到达叶子节点(即位置 x)
        tr_sum[u] += v  # 增加 v
    else:
        mid = (tr_l[u] + tr_r[u]) // 2
        # 判断 x 在左子树还是右子树
        if x <= mid:
            modify(u << 1, x, v)      # 递归修改左子树
        else:
            modify(u << 1 | 1, x, v)  # 递归修改右子树
        # 修改完子树后,向上更新当前节点的和
        pushup(u)


# ========== 主程序 ==========
# 读入数字个数 n 和操作次数 m
n, m = map(int, input().split())

# 读入原始数组,并转换为 1-indexed(w[0] 不用,w[1] ~ w[n] 有效)
w = [0]  # w[0] = 0(占位)
w.extend(list(map(int, input().split())))  # w[1] 到 w[n] 存入数据

# 从根节点(编号 1)开始,构建覆盖区间 [1, n] 的线段树
build(1, 1, n)

# 存储所有查询结果,避免频繁 print(提高效率)
out_lines = []

# 处理 m 次操作
for _ in range(m):
    k, a, b = map(int, input().split())
    if k == 0:
        # 查询操作:求区间 [a, b] 的和
        res = query(1, a, b)
        out_lines.append(str(res))
    else:
        # 修改操作:将位置 a 的值增加 b
        modify(1, a, b)

# 输出所有查询结果
for line in out_lines:
    print(line)

测试数据

10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8

截图

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

©2026加盟我们 | 关于我们 | ACM课程 | 常见问题 | 成果墙 | 评测记录 | 浙ICP备2021013995号
在线画图 | OI WIki | 打字练习
火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码