线段树
丧失修改:ST 表
丧失差分:树状数组
丧失 \(k\) 大:堆
所以,我们需要线段树!
让我们分析一下:区间修改不能影响太多区间,区间查询也是一样。
所以线段树,就是通过只保留重要的区间来快速查询的一个算法。
如下是一个 \(N = 8\) 的线段树:
(好吧,画的有点不行,但这是值得的(我有了无限草稿纸))
线段树的区间查询,就是 DFS,DFS 到一个子区间直接返回并合并答案,DFS 到一个不交的区间直接返回(不合并)。
但是,没有修改的数据结构是没有灵魂的——hhc,线段树最实用的地方还是它加上区间修改后仍然能保证 \(O(\log N)\)(而不是暴力的 \(O(N \log N)\))。
区间修改
我们引入一个东西,叫做懒标记(可以扩展!)。对于某个区间的修改,我们一样拆成一些子区间然后对这些子区间(及其上面)处理修改。而子区间下面的信息则由懒标记来维护。
不管怎样,原则要加上一条:如果我们访问到的节点有懒标记,并且这个节点还要往下递归,那么下传懒标记。
我打个比方吧,懒标记就像是上司欠员工的钱。
领导下来巡查了,上级瑟瑟发抖,于是还清了自己的债务(这就是懒标记下传)。
常见错误
-
懒标记下传后未清空
-
没判断两个线段不交
LG 3373 【模板】线段树 2 link:https://www.luogu.com.cn/problem/P3373
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, q, m, arr[100010], d[500050], op, x, y, k;
struct tag {
int mul, add;
}t[500050];
struct mergifyd {
int operator()(int a, int b) {
return (a + b) % m;
}
}mrgd;
struct mergifyt {
tag operator()(tag a, tag b) {
return {a.mul * b.mul % m, (a.add * b.mul + b.add) % m};
}
}mrgt;
void pushdn(int id, int l, int r) {
t[id * 2] = mrgt(t[id * 2], t[id]);
t[id * 2 + 1] = mrgt(t[id * 2 + 1], t[id]);
int mid = (l + r) >> 1;
d[id * 2] = (d[id * 2] * t[id].mul + t[id].add * (mid - l + 1)) % m;
d[id * 2 + 1] = (d[id * 2 + 1] * t[id].mul + t[id].add * (r - mid)) % m;
t[id] = {1, 0};
}
void build(int id, int l, int r) {
t[id] = {1, 0};
if(l == r) {
d[id] = arr[l];
return ;
}
int mid = (l + r) >> 1;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
d[id] = mrgd(d[id * 2], d[id * 2 + 1]);
//cout << id << ' ' << d[id] << '\n';
}
void mul(int id, int l, int r, int ql, int qr, int x) {
if(r < ql || qr < l) {
return ;
}
if(ql <= l && r <= qr) {
t[id] = mrgt(t[id], {x, 0});
d[id] = (d[id] * x) % m;
return ;
}
pushdn(id, l, r);
int mid = (l + r) >> 1;
mul(id * 2, l, mid, ql, qr, x);
mul(id * 2 + 1, mid + 1, r, ql, qr, x);
d[id] = mrgd(d[id * 2], d[id * 2 + 1]);
}
void add(int id, int l, int r, int ql, int qr, int x) {
//cout << id << ' ' << l << ' ' << r << ' ' << ql << ' ' << qr << ' ' << x << '\n';
if(r < ql || qr < l) {
//cout << "out" << '\n';
return ;
}
if(ql <= l && r <= qr) {
//cout << "in" << '\n';
t[id] = mrgt(t[id], {1, x});
d[id] = (d[id] + (r - l + 1) * x) % m;
return ;
}
pushdn(id, l, r);
int mid = (l + r) >> 1;
add(id * 2, l, mid, ql, qr, x);
add(id * 2 + 1, mid + 1, r, ql, qr, x);
d[id] = mrgd(d[id * 2], d[id * 2 + 1]);
}
int query(int id, int l, int r, int ql, int qr) {
if(r < ql || qr < l) {
return 0;
}
if(ql <= l && r <= qr) {
return d[id];
}
pushdn(id, l, r);
int mid = (l + r) >> 1;
return mrgd(query(id * 2, l, mid, ql, qr), query(id * 2 + 1, mid + 1, r, ql, qr));
}
signed main() {
cin >> n >> q >> m;
for(int i = 1; i <= n; i++) {
cin >> arr[i];
}
build(1, 1, n);
//for(int i = 1; i <= 9; i++) {
//cout << d[i] << ',' << t[i].mul << ',' << t[i].add << ' ';
//}
//cout << '\n';
for(; q--; ) {
cin >> op >> x >> y;
if(op == 3) {
cout << query(1, 1, n, x, y) << '\n';
}else {
cin >> k;
if(op == 1) {
mul(1, 1, n, x, y, k);
}else {
add(1, 1, n, x, y, k);
}
}
//for(int i = 1; i <= 9; i++) {
//cout << d[i] << ',' << t[i].mul << ',' << t[i].add << ' ';
//}
//cout << '\n';
}
return 0;
}