You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现前缀加1操作下带权累积平方和的O(logn)单次查询解法?

嘿,这个问题其实是区间更新+动态查询总和的经典场景,核心是先把问题转化成数学表达式,再找合适的数据结构来高效维护状态。我来一步步给你拆解解决方案:

问题转化与数学分析

首先你得注意到:数组A里的每个元素A[i],本质上就是有多少次操作的k是大于等于i的——毕竟每次操作k都会给0到k的元素加1,所以A[i]就是被这类操作覆盖的次数。我们把这个次数记为cnt[i],那题目要求返回的结果就可以写成:

total = Σ (cnt[i]² * W[i]) ,其中i从0到n-1

现在每次执行操作k,就是给cnt[0]到cnt[k]的每个值都加1。我们需要快速算出操作后total的新值。先推导一下这次操作会让total增加多少:
对于每个i ≤ k,(cnt[i]+1)²*W[i] - cnt[i]²*W[i] = (2*cnt[i] + 1)*W[i]。把这些增量加起来,总变化量就是:

Δtotal = Σ (2*cnt[i] + 1)*W[i] (i从0到k)
= 2*Σ(cnt[i]*W[i]) + ΣW[i] (i从0到k)

这就意味着,只要能快速拿到两个值:

  1. preW[k]:W[0]到W[k]的总和(这是固定值,提前预处理好就行)
  2. preCW[k]:cnt[0]*W[0]到cnt[k]*W[k]的总和(这个是动态变化的,得高效维护)

就能算出每次操作的增量,进而更新total。但问题是,操作后所有包含0..k的前缀和都会增加preW[k],如果直接维护前缀和数组,每次操作都是O(n)复杂度,显然不符合要求。这时候线段树就是最优的选择了。

线段树解决方案

我们可以用线段树来维护每个区间的三个核心状态,同时用懒标记来处理区间更新的延迟传递,保证每次操作都是O(logn)复杂度。

线段树节点要存啥?

每个节点代表一个区间[l, r],需要维护:

  • sumW:这个区间里所有W[i]的总和(固定值,建树时就确定了)
  • sumCW:这个区间里cnt[i]*W[i]的总和(动态变化)
  • sumC2W:这个区间里cnt[i]²*W[i]的总和(就是我们最终要的总和的一部分)
  • lazy:懒标记,记录这个区间里所有cnt[i]需要加的次数(还没传递给子节点)

区间更新怎么处理?

当我们给一个区间的cnt[i]加1时,三个动态值的更新规则很清晰:

  1. sumC2W = sumC2W + 2*sumCW + sumW
    (因为每个cnt[i]变成cnt[i]+1,平方项展开后,每个元素的增量是2*cnt[i]+1,乘以W[i]求和就是2*sumCW + sumW)
  2. sumCW = sumCW + sumW
    (每个cnt[i]*W[i]都增加了W[i],所以总和增加整个区间的W之和)
  3. 如果当前节点不是叶子节点,就把懒标记加1,留到需要访问子节点时再传递。

懒标记怎么下推?

当我们需要访问当前节点的子节点时,如果懒标记不为0,就得把这个标记传递下去:

  • 分别给左、右子节点执行上述的区间加1操作(用当前的懒标记值,不是只加1)
  • 然后把当前节点的懒标记重置为0

查询结果

我们要的total就是线段树根节点的sumC2W,每次操作后直接读这个值就行,O(1)搞定。

复杂度分析

  • 建树时间:O(n)
  • 单次操作(区间更新+读取结果):O(logn),完全满足题目要求的复杂度。

伪代码实现参考

class SegmentTreeNode:
    def __init__(self, l, r):
        self.l = l
        self.r = r
        self.left = None
        self.right = None
        self.sumW = 0
        self.sumCW = 0
        self.sumC2W = 0
        self.lazy = 0

def build_segment_tree(l, r, W):
    node = SegmentTreeNode(l, r)
    if l == r:
        node.sumW = W[l]
        return node
    mid = (l + r) // 2
    node.left = build_segment_tree(l, mid, W)
    node.right = build_segment_tree(mid+1, r, W)
    node.sumW = node.left.sumW + node.right.sumW
    return node

def push_lazy(node):
    if node.lazy == 0 or not node.left:
        return
    # 更新左子节点
    left = node.left
    left.sumC2W += 2 * left.sumCW + left.sumW * node.lazy
    left.sumCW += left.sumW * node.lazy
    left.lazy += node.lazy
    # 更新右子节点
    right = node.right
    right.sumC2W += 2 * right.sumCW + right.sumW * node.lazy
    right.sumCW += right.sumW * node.lazy
    right.lazy += node.lazy
    # 重置当前节点的懒标记
    node.lazy = 0

def update_range(node, target_l, target_r):
    if node.r < target_l or node.l > target_r:
        return
    if target_l <= node.l and node.r <= target_r:
        node.sumC2W += 2 * node.sumCW + node.sumW
        node.sumCW += node.sumW
        node.lazy += 1
        return
    push_lazy(node)
    update_range(node.left, target_l, target_r)
    update_range(node.right, target_l, target_r)
    # 合并子节点的状态
    node.sumCW = node.left.sumCW + node.right.sumCW
    node.sumC2W = node.left.sumC2W + node.right.sumC2W

def get_total_value(node):
    return node.sumC2W

使用示例

  1. 用你的W数组构建线段树:root = build_segment_tree(0, n-1, W)
  2. 每次操作k:先调用update_range(root, 0, k),然后调用get_total_value(root)就能得到结果。

进阶替代方案:前缀和+差分优化

如果你不想用线段树,也可以通过数学变形,用前缀和和差分数组来维护状态,但这个方法的推导和维护逻辑相对复杂,不如线段树直观。简单说就是:

  • 用差分数组diff维护cnt的变化:每次操作k,diff[0] +=1,如果k+1 <n就diff[k+1] -=1
  • 预处理W的后缀和suffixW[j] = Σ_{i=j}^{n-1} W[i]
  • 维护两个变量来计算最终的total,具体推导这里就不展开了,感兴趣可以自己试试。

内容的提问来源于stack exchange,提问作者piedpiper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 09:22:45