线段树

  • 参考:线段树基础 - OI Wiki

  • 线段树可以在 \(𝑂(log⁡𝑁)\) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

  • 是常用的维护区间信息的数据结构。

YsoaQMBuI82ULSd.png

建树

  • 线段树将每个长度不为 1 的区间划分成左右两个区间递归求解,把整个线段划分为一个树形结构,通过合并左右两区间信息来求得该区间的信息。
// 从int a[N]数组中构建当前节点编号为p,管辖区间为[l,r]的节点
void _build(int s, int t, int p, ll a[]) {
	// 当区间的左右端点重合时,直接更新该点即可
	if (s == t) {
		d[p] = a[s];
		return;
	}
	// 将区间分为两个部分[s,mid],[mid+1,t],分别更新后更新[s,t]区间
	int m = ((t - s) >> 1) + s;
	// 区间[s,mid]对应节点为2*p
	_build(s, m, 2 * p, a);
	// 区间[mid+1,t]对应节点为2*p+1
	_build(m + 1, t, 2 * p + 1, a);
	// 最后更新[s,t]区间
	d[p] = d[2 * p] + d[2 * p + 1];
}

区间更新

  • 当要求更新区间\([l,r]\)中的所有数时,一个个遍历更新是十分低效的。
  • 引入一个懒惰标记,通过延迟对节点信息的更改,从而减少可能不必要的操作次数。
  • 更新一个节点时,通过打标记的方法表明该节点对应的区间在某一次操作中被更改,但不更新该节点的子节点的信息
  • 实质性的修改则在下一次访问带有标记的节点时才进行。
// 封装下推懒标记的函数,将管辖区间为[s,t]的节点p的懒标记下推
void push(int s, int t, int p) {
	// 将区间划分为[s,m],[m+1,t]
	int m = ((t - s) >> 1) + s;
	// 如果存在懒标记,且不为叶子节点则下推懒标记
	if (lazy[p] && s != t) {
		// 更新两个孩子节点
		d[2 * p] += (m - s + 1) * lazy[p];
		d[2 * p + 1] += (t - m) * lazy[p];
		// 更新两个孩子节点的懒标记
		lazy[2 * p] += lazy[p];
		lazy[2 * p + 1] += lazy[p];
		// 清空当前节点懒标记
		lazy[p] = 0;
	}
}

// 将区间[l,r]的数都加上c; p是当前节点编号,管辖区间[s,t]
void _update(int l, int r, ll c, int s, int t, int p) {
	// 如果[s,t]是[l,r]的子集,则直接更新并带上懒标记。
	if (l <= s && t <= r) {
		d[p] += (t - s + 1) * c;
		lazy[p] += c;
		return;
	}
	// 否则考虑两个子区间[s,m],[m+1,t]
	int m = ((t - s) >> 1) + s;
	// 访问了节点p,下推懒标记,更新子节点
	push(s, t, p);
	// 如果[l,r]与[s,m]有交集,更新对应子节点
	if (l <= m) {
		_update(l, r, c, s, m, 2 * p);
	}
	// 如果[l,r]与[m+1,t]有交集,更新对应子节点
	if (r > m) {
		_update(l, r, c, m + 1, t, 2 * p + 1);
	}
	// 使用更新后的子节点更新自身。
	d[p] = d[2 * p] + d[2 * p + 1];
}

区间查询

// 查询区间为[l,r]的区间和;当前节点编号为p,管辖区间[s,t]
ll _query(int l, int r, int s, int t, int p) {
	// 如果[s,t]是[l,r]的子集,直接返回结果
	if (l <= s && t <= r) {
		return d[p];
	}
	// 否则考虑[s,m],[m+1,t]两个区间。
	int m = ((t - s) >> 1) + s;
	// 访问了节点p,下推懒标记
	push(s, t, p);
	// 记录答案
	ll ans = 0;
	// 如果[s,m]与[l,r]有交集,向下访问子节点2*p
	if (l <= m) {
		ans = _query(l, r, s, m, 2 * p);
	}
	// 如果[m+1,t]与[l,r]有交集,向下访问子节点2*p+1
	if (r > m) {
		ans += _query(l, r, m + 1, t, 2 * p + 1);
	}
	//返回答案
	return ans;
}

封装结构(区间和,区间加)

struct segTree {
    ll d[4 * N];
    ll lazy[4 * N];
    int n;

    void _build(int s, int t, int p, ll a[]) {
        if (s == t) {
            d[p] = a[s];
            return;
        }
        int m = ((t - s) >> 1) + s;
        _build(s, m, 2 * p, a);
        _build(m + 1, t, 2 * p + 1, a);
        d[p] = d[2 * p] + d[2 * p + 1];
    }

    void push(int s, int t, int p) {
        int m = ((t - s) >> 1) + s;
        if (lazy[p] && s != t) {
            d[2 * p] += (m - s + 1) * lazy[p];
            d[2 * p + 1] += (t - m) * lazy[p];
            lazy[2 * p] += lazy[p];
            lazy[2 * p + 1] += lazy[p];
            lazy[p] = 0;
        }
    }

    void _update(int l, int r, ll c, int s, int t, int p) {
        if (l <= s && t <= r) {
            d[p] += (t - s + 1) * c;
            lazy[p] += c;
            return;
        }

        int m = ((t - s) >> 1) + s;
        push(s, t, p);

        if (l <= m) {
            _update(l, r, c, s, m, 2 * p);
        }
        if (r > m) {
            _update(l, r, c, m + 1, t, 2 * p + 1);
        }
        d[p] = d[2 * p] + d[2 * p + 1];
    }

    ll _query(int l, int r, int s, int t, int p) {
        if (l <= s && t <= r) {
            return d[p];
        }
        int m = ((t - s) >> 1) + s;
        push(s, t, p);

        ll ans = 0;
        if (l <= m) {
            ans = _query(l, r, s, m, 2 * p);
        }
        if (r > m) {
            ans += _query(l, r, m + 1, t, 2 * p + 1);
        }
        return ans;
    }

    void build(int n, ll a[]) {
        this->n = n;
        memset(lazy, 0, sizeof(lazy));
        _build(1, n, 1, a);
    }

    void update(int l, int r, int c) {
        _update(l, r, c, 1, n, 1);
    }

    ll query(int l, int r) {
        return _query(l, r, 1, n, 1);
    }

} tree;

例题

#include <bits/stdc++.h>
#define ll long long
#define int long long
#define IOS \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);
#define endl '\n'
int const inf = 0x3f3f3f3f;
ll const infll = 0x3f3f'3f3f'3f3f'3f3f;
double const PI = acos(-1.0);
using namespace std;
// ifstream fin("input.txt");
// ofstream fout("output.txt");
// #define cin fin
// #define cout fout

int const N = 1e5 + 10;
int n, m;
ll a[N];

struct segTree {
    ll d[4 * N];
    ll lazy[4 * N];
    int n;

    void _build(int s, int t, int p, ll a[]) {
        if (s == t) {
            d[p] = a[s];
            return;
        }
        int m = ((t - s) >> 1) + s;
        _build(s, m, 2 * p, a);
        _build(m + 1, t, 2 * p + 1, a);
        d[p] = d[2 * p] + d[2 * p + 1];
    }

    void push(int s, int t, int p) {
        int m = ((t - s) >> 1) + s;
        if (lazy[p] && s != t) {
            d[2 * p] += (m - s + 1) * lazy[p];
            d[2 * p + 1] += (t - m) * lazy[p];
            lazy[2 * p] += lazy[p];
            lazy[2 * p + 1] += lazy[p];
            lazy[p] = 0;
        }
    }

    void _update(int l, int r, ll c, int s, int t, int p) {
        if (l <= s && t <= r) {
            d[p] += (t - s + 1) * c;
            lazy[p] += c;
            return;
        }

        int m = ((t - s) >> 1) + s;
        push(s, t, p);

        if (l <= m) {
            _update(l, r, c, s, m, 2 * p);
        }
        if (r > m) {
            _update(l, r, c, m + 1, t, 2 * p + 1);
        }
        d[p] = d[2 * p] + d[2 * p + 1];
    }

    ll _query(int l, int r, int s, int t, int p) {
        if (l <= s && t <= r) {
            return d[p];
        }
        int m = ((t - s) >> 1) + s;
        push(s, t, p);

        ll ans = 0;
        if (l <= m) {
            ans = _query(l, r, s, m, 2 * p);
        }
        if (r > m) {
            ans += _query(l, r, m + 1, t, 2 * p + 1);
        }
        return ans;
    }

    void build(int n, ll a[]) {
        this->n = n;
        memset(lazy, 0, sizeof(lazy));
        _build(1, n, 1, a);
    }

    void update(int l, int r, int c) {
        _update(l, r, c, 1, n, 1);
    }

    ll query(int l, int r) {
        return _query(l, r, 1, n, 1);
    }

} tree;

signed main() {
    IOS cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    tree.build(n, a);
    while (m--) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y;
            ll k;
            cin >> x >> y >> k;
            tree.update(x, y, k);
        } else {
            int x, y;
            cin >> x >> y;
            cout << tree.query(x, y) << endl;
        }
    }

    // fin.close(),fout.close();
    return 0;
}
#include <bits/stdc++.h>
#define ll long long
// #define int long long
#define IOS \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);
#define endl '\n'
int const inf = 0x3f3f3f3f;
ll const infll = 0x3f3f'3f3f'3f3f'3f3f;
double const PI = acos(-1.0);
using namespace std;
// ifstream fin("input.txt");
// ofstream fout("output.txt");
// #define cin fin
// #define cout fout

int const N = 1e5 + 10;
int n, q, m;
int a[N];

inline int add(int x, int y) {
    return (x + y * 1LL) % m;
}

inline int mul(int x, int y) {
    return (1LL * x * y ) % m;
}

struct segTree {
    int d[4 * N];
    // 使用多个懒标记进行多种运算
    int lazy[4 * N];  // 加法懒标记
    int lzmul[4 * N]; // 乘法懒标记
    int n;

    void _build(int s, int t, int p, int a[]) {
        lzmul[p] = 1;
        if (s == t) {
            d[p] = a[s];
            return;
        }
        int mid = ((t - s) >> 1) + s;
        _build(s, mid, 2 * p, a);
        _build(mid + 1, t, 2 * p + 1, a);
        d[p] = add(d[2 * p], d[2 * p + 1]);
    }
	// 下推懒惰标记
    void pushdown(int s, int t, int p) {
        int mid = ((t - s) >> 1) + s;
        if (lzmul[p] != 1 && s != t) {
            d[2 * p] = mul(d[2 * p], lzmul[p]);
            d[2 * p + 1] = mul(d[2 * p + 1], lzmul[p]);
            lzmul[2 * p] = mul(lzmul[2 * p], lzmul[p]);
            lzmul[2 * p + 1] = mul(lzmul[2 * p + 1], lzmul[p]);
            // 懒标记传递
            lazy[2 * p] = mul(lazy[2 * p], lzmul[p]);
            lazy[2 * p + 1] = mul(lazy[2 * p + 1], lzmul[p]);
            lzmul[p] = 1;
        }
        if (lazy[p] && s != t) {
            d[2 * p] = add(d[2 * p], mul((mid - s + 1), lazy[p]));
            d[2 * p + 1] = add(d[2 * p + 1], mul((t - mid), lazy[p]));
            lazy[2 * p] = add(lazy[2 * p], lazy[p]);
            lazy[2 * p + 1] = add(lazy[2 * p + 1], lazy[p]);
            lazy[p] = 0;
        }
    }

    void jia(int l, int r, int c, int s, int t, int p) {
        if (l <= s && t <= r) {
            d[p] = add(d[p], mul(c, (t - s + 1)));
            lazy[p] = add(lazy[p], c);
            return;
        }
        int mid = ((t - s) >> 1) + s;
        pushdown(s, t, p);
        if (l <= mid) {
            jia(l, r, c, s, mid, 2 * p);
        }
        if (r >= mid + 1) {
            jia(l, r, c, mid + 1, t, 2 * p + 1);
        }
        d[p] = add(d[2 * p], d[2 * p + 1]);
    }

    void chen(int l, int r, int c, int s, int t, int p) {
        if (l <= s && t <= r) {
            d[p] = mul(d[p], c);
            lzmul[p] = mul(lzmul[p], c);
            // 懒标记传递
            lazy[p] = mul(lazy[p], c);
            return;
        }
        int mid = ((t - s) >> 1) + s;
        pushdown(s, t, p);
        if (l <= mid) {
            chen(l, r, c, s, mid, 2 * p);
        }
        if (r >= mid + 1) {
            chen(l, r, c, mid + 1, t, 2 * p + 1);
        }
        d[p] = add(d[2 * p], d[2 * p + 1]);
    }

    int query(int l, int r, int s, int t, int p) {
        if (l <= s && t <= r) {
            return d[p];
        }
        int mid = ((t - s) >> 1) + s;
        pushdown(s, t, p);
        int ans = 0;
        if (l <= mid) {
            ans = add(ans, query(l, r, s, mid, 2 * p));
        }
        if (r >= mid + 1) {
            ans = add(ans, query(l, r, mid + 1, t, 2 * p + 1));
        }
        return ans;
    }

} tree;

signed main() {
    IOS cin >> n >> q >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    tree.n = n;
    tree._build(1, n, 1, a);
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y, k;
            cin >> x >> y >> k;
            tree.chen(x, y, k, 1, n, 1);
        } else if (op == 2) {
            int x, y, k;
            cin >> x >> y >> k;
            tree.jia(x, y, k, 1, n, 1);
        } else {
            int x, y;
            cin >> x >> y;
            cout << tree.query(x, y, 1, n, 1) << endl;
        }
    }
    // fin.close(),fout.close();
    return 0;
}

动态开点线段树

  • 堆式储存的情况下,需要给线段树开 \(4𝑛\) 大小的数组。为了节省空间,我们可以不一次性建好树,而是在最初只建立一个根结点代表整个区间。当我们需要访问某个子区间时,才建立代表这个区间的子结点。
  • 动态开点线段树的核心思想就是:结点只有在有需要的时候才被创建
  • 线段树基础 - OI Wiki
posted @ 2025-11-02 22:54  NightRainLone  阅读(20)  评论(0)    收藏  举报