NeFut Logo NeFut
Admin Login

Algebraic Maintenance and Square Sum Update Strategies for Lazy Segment Trees

Published at: 2026-05-27 09:17 Last updated: 2026-06-11 07:37
#C++ #Tutorial

Core Logic and Mathematical Principles

When a segment tree needs to maintain multiple interval modification operations simultaneously (such as both multiplication and addition modifications in the same interval) or maintain nonlinear information (such as the sum of squares in an interval), the core difficulty lies in defining the algebraic priority of multiple tags and the algebraic expansion of nonlinear states.

1. Algebraic Maintenance of Mixed Interval Multiplication and Addition

Assuming the current node maintains an interval sum of $S$, and this node has been subjected to operations of multiplying by a number $m$ and adding a number $a$. To represent the state at any moment with a unified algebraic expression, we define the composite transformation as:

$$S_{new} = S \times m + a \times len$$

where $len$ is the current interval length.

If at this time the interval receives another multiplication operation (multiplying by $M$) and an addition operation (adding $A$), based on the associative and distributive laws, the new state is:

$$S_{final} = (S \times m + a \times len) \times M + A \times len = S \times (m \times M) + (a \times M + A) \times len$$

From this, we derive the hardcore rule for merging multiple tags:

Conclusion: The multiplication tag directly alters the existing addition tag, while the addition tag does not affect the existing multiplication tag. Therefore, the priority of the multiplication tag is higher than that of the addition tag. When passing down tags, multiplication must be handled first, followed by addition.

2. Algebraic Expansion of the Sum of Squares in Intervals

When the segment tree maintains the sum of squares $\\sum X_i^2$, if an operation adds $v$ to the interval, each element $X_i \leftarrow X_i + v$. Using the complete square formula:

$$\sum (X_i + v)^2 = \sum (X_i^2 + 2vX_i + v^2) = \sum X_i^2 + 2v \sum X_i + v^2 \sum 1$$

The state transition equation becomes:

$$S_{sq\text{new}} = S_{sq} + 2v \times S_{sum} + v^2 \times len$$

This means that to maintain the sum of squares in an interval, the first-order sum ($S_{sum}$) must also be maintained.


State Design and Algorithm Derivation

1. State of the Mixed Multiplication and Addition Segment Tree

Each node maintains three values: sum (interval sum), mul (multiplication lazy tag, initialized to $1$), add (addition lazy tag, initialized to $0$).

Tag Passing Down (Pushdown) Derivation

For the left child node $ls$ of node $u$ ($ls = u \ll 1$):

  1. Update the child node's data value (must strictly follow the order of multiplication first, then addition):

$$sum[ls] = (sum[ls] \times mul[u] + add[u] \times len_{ls}) \pmod P$$

  1. Update the child node's tag values:

$$mul[ls] = (mul[ls] \times mul[u]) \pmod P$$

$$add[ls] = (add[ls] \times mul[u] + add[u]) \pmod P$$

  1. The same applies to the right child node. After passing down, restore the physical baseline state: mul[u] = 1, add[u] = 0.

2. State of the Segment Tree for the Sum of Squares in Intervals

Each node maintains: sum1 (first-order sum $\\sum X_i$), sum2 (second-order sum of squares $\\sum X_i^2$), lazy (addition tag).

Tag Passing Down (Pushdown) Derivation

For child node $ls$, if the parent node has an addition tag $v$:

  1. First, update the second-order sum based on the first-order sum: $sum2[ls] = sum2[ls] + 2 \times v \times sum1[ls] + v \times v \times len_{ls}$
  2. Then update the first-order sum: $sum1[ls] = sum1[ls] + v \times len_{ls}$
  3. Accumulate the lazy tag: $lazy[ls] = lazy[ls] + v$

C++ Standard Source Code (NOIP Style: Taking Interval Multiplication + Addition as an Example)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

inline long long read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

const int MAXN = 100005;
long long sum[MAXN << 2];
long long mul[MAXN << 2];
long long add[MAXN << 2];
long long a[MAXN];
long long MOD; // NOIP mixed common large prime modulus

inline void pushup(int u) {
    sum[u] = (sum[u << 1] + sum[u << 1 | 1]) % MOD;
}

// Core mixed pushdown
inline void pushdown(int u, int l, int r) {
    int m = (l + r) >> 1;
    int ls = u << 1, rs = u << 1 | 1;

    // 1. Handle the left subtree
    sum[ls] = (sum[ls] * mul[u] % MOD + add[u] * (m - l + 1) % MOD) % MOD;
    mul[ls] = (mul[ls] * mul[u]) % MOD;
    add[ls] = (add[ls] * mul[u] % MOD + add[u]) % MOD; // Critical pitfall: the original addition tag of the child node must first be multiplied by the multiplication tag of the parent node

    // 2. Handle the right subtree
    sum[rs] = (sum[rs] * mul[u] % MOD + add[u] * (r - m) % MOD) % MOD;
    mul[rs] = (mul[rs] * mul[u]) % MOD;
    add[rs] = (add[rs] * mul[u] % MOD + add[u]) % MOD;

    // 3. Reset the current node's tags
    mul[u] = 1;
    add[u] = 0;
}

void build(int u, int l, int r) {
    mul[u] = 1; // Critical pitfall: the multiplication tag must be initialized to 1; mistakenly initializing to 0 will cause the entire tree's data to be erased
    add[u] = 0;
    if (l == r) {
        sum[u] = a[l] % MOD;
        return;
    }
    int m = (l + r) >> 1;
    build(u << 1, l, m);
    build(u << 1 | 1, m + 1, r);
    pushup(u);
}

void update_mul(int u, int l, int r, int ql, int qr, long long v) {
    if (ql <= l && r <= qr) {
        sum[u] = (sum[u] * v) % MOD;
        mul[u] = (mul[u] * v) % MOD;
        add[u] = (add[u] * v) % MOD; // The multiplication operation directly affects the current node's addition tag
        return;
    }
    pushdown(u, l, r);
    int m = (l + r) >> 1;
    if (ql <= m) update_mul(u << 1, l, m, ql, qr, v);
    if (qr > m) update_mul(u << 1 | 1, m + 1, r, ql, qr, v);
    pushup(u);
}

void update_add(int u, int l, int r, int ql, int qr, long long v) {
    if (ql <= l && r <= qr) {
        sum[u] = (sum[u] + v * (r - l + 1) % MOD) % MOD;
        add[u] = (add[u] + v) % MOD; // The addition operation does not affect the current node's multiplication tag
        return;
    }
    pushdown(u, l, r);
    int m = (l + r) >> 1;
    if (ql <= m) update_add(u << 1, l, m, ql, qr, v);
    if (qr > m) update_add(u << 1 | 1, m + 1, r, ql, qr, v);
    pushup(u);
}

long long query(int u, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) return sum[u];
    pushdown(u, l, r);
    int m = (l + r) >> 1;
    long long res = 0;
    if (ql <= m) res = (res + query(u << 1, l, m, ql, qr)) % MOD;
    if (qr > m) res = (res + query(u << 1 | 1, m + 1, r, ql, qr)) % MOD;
    return res;
}

int main() {
    int n = read(), m = read();
    MOD = read();
    for (int i = 1; i <= n; ++i) a[i] = read();

    build(1, 1, n);

    while (m--) {
        int opt = read();
        if (opt == 1) {
            int ql = read(), qr = read();
            long long v = read();
            update_mul(1, 1, n, ql, qr, v);
        } else if (opt == 2) {
            int ql = read(), qr = read();
            long long v = read();
            update_add(1, 1, n, ql, qr, v);
        } else {
            int ql = read(), qr = read();
            printf("%lld\n", query(1, 1, n, ql, qr));
        }
    }
    return 0;
}

NOIP 实战避坑指南

1. 乘法标记错误的初始化

在多标记线段树中,选手常习惯性地在 build 或声明时将所有数组清零。如果将乘法标记 mul[] 误初始化为 0,执行 pushdown 时,sum[ls] * mul[u] 将使得所有子树节点的数据直接变成 0必须在建树时,显式将所有节点的 mul[u] 赋值为 1

2. 标记下传时的取模非原子性

在执行 add[ls] = (add[ls] * mul[u] + add[u]) % MOD; 时,乘法和加法交织在一起。极易出现的低级错误是漏掉中间步骤的取模,导致 add[ls] * mul[u] 直接溢出 long long(最大可达 $(10^9) \times (10^9) = 10^{18}$,若连续两个未取模的大数相乘会直接截断)。每一阶段的乘法和加法结果都必须立刻包裹 % MOD

3. 平方和维护中的更新顺序颠倒

在线段树维护区间平方和的 pushdown 中,必须先更新平方和 sum2,再更新一阶和 sum1。因为 sum2 的更新公式里包含 sum1(即 $2v \times sum1$)。如果先将 sum1 加上了 $v \times len$,此时的 sum1 已经是未来的状态,再代入 sum2 的计算公式就会引发重复计算,导致数学逻辑彻底崩塌。


经典 NOIP/洛谷 真题

1. 洛谷 P3373 【模板】线段树 2

2. 洛谷 P1471 矩形方差 / 洛谷 P6327 区间正弦和(变种拓展)


[h] Back to Home