模板一:区间加法 + 区间求和(取模) + 区间乘法
例题:P3372 【模板】线段树 1 - 洛谷和P3373 【模板】线段树 2 - 洛谷
代码如下
cpp
#include<bits/stdc++.h>
using namespace std;
// 所有变量用 long long,防止大数溢出
#define int long long
#define endl '\n'
// 数组最大长度,根据题目 n 调整
const int N = 1e5 + 10;
int sum[N << 2]; // 线段树节点:维护对应区间的总和
int mul[N << 2]; // 乘法懒标记,初值为 1
int add[N << 2]; // 加法懒标记,初值为 0
int a[N]; // 原始输入数组
int mod; // 题目给定模数
/**
* @brief 懒标记下传:先传乘法,再传加法
* @param u 当前线段树节点编号
* @param l,r 当前节点管辖的区间 [l, r]
*/
void pushdown(int u, int l, int r)
{
// 无任何延迟操作,直接返回
if (mul[u] == 1 && add[u] == 0) return;
int mid = (l + r) / 2;
int left = u << 1; // 左子节点编号
int right = u << 1 | 1; // 右子节点编号
// ========== 更新左子树 ==========
// 区间和:先乘标记,再加标记*长度
sum[left] = (sum[left] * mul[u] + add[u] * (mid - l + 1)) % mod;
// 乘法标记叠加
mul[left] = (mul[left] * mul[u]) % mod;
// 加法标记:先乘原有乘法,再加当前加法
add[left] = (add[left] * mul[u] + add[u]) % mod;
// ========== 更新右子树 ==========
sum[right] = (sum[right] * mul[u] + add[u] * (r - mid)) % mod;
mul[right] = (mul[right] * mul[u]) % mod;
add[right] = (add[right] * mul[u] + add[u]) % mod;
// 清空当前节点标记
mul[u] = 1;
add[u] = 0;
}
/**
* @brief 线段树建树
* @param u 当前节点编号
* @param l,r 当前节点管辖区间
*/
void build(int u, int l, int r)
{
mul[u] = 1; // 乘法标记初始为1(乘1无变化)
add[u] = 0; // 加法标记初始为0(加0无变化)
// 叶子节点:直接赋值原数组元素
if (l == r)
{
sum[u] = a[l] % mod;
return;
}
int mid = (l + r) / 2;
build(u << 1, l, mid); // 递归构建左子树
build(u << 1 | 1, mid + 1, r); // 递归构建右子树
// 当前区间和 = 左子树和 + 右子树和
sum[u] = (sum[u << 1] + sum[u << 1 | 1]) % mod;
}
/**
* @brief 区间乘法:对 [L, R] 所有数乘 val
* @param u 当前节点编号
* @param l,r 当前节点区间
* @param L,R 目标修改区间
* @param val 乘数
*/
void update_mul(int u, int l, int r, int L, int R, int val)
{
if (L <= l && r <= R)
{
sum[u] = (sum[u] * val) % mod;
mul[u] = (mul[u] * val) % mod;
add[u] = (add[u] * val) % mod;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update_mul(u << 1, l, mid, L, R, val);
if (R > mid) update_mul(u << 1 | 1, mid + 1, r, L, R, val);
sum[u] = (sum[u << 1] + sum[u << 1 | 1]) % mod;
}
/**
* @brief 区间加法:对 [L, R] 所有数加上 val
* @param u 当前节点编号
* @param l,r 当前节点区间
* @param L,R 目标修改区间
* @param val 要增加的值
*/
void update_add(int u, int l, int r, int L, int R, int val)
{
if (L <= l && r <= R)
{
sum[u] = (sum[u] + val * (r - l + 1)) % mod;
add[u] = (add[u] + val) % mod;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update_add(u << 1, l, mid, L, R, val);
if (R > mid) update_add(u << 1 | 1, mid + 1, r, L, R, val);
sum[u] = (sum[u << 1] + sum[u << 1 | 1]) % mod;
}
/**
* @brief 区间查询:查询 [L, R] 的区间和
* @return 区间总和
*/
int query(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return sum[u] % mod;
pushdown(u, l, r);
int mid = (l + r) / 2;
int res = 0;
if (L <= mid) res = (res + query(u << 1, l, mid, L, R)) % mod;
if (R > mid) res = (res + query(u << 1 | 1, mid + 1, r, L, R)) % mod;
return res;
}
void solve()
{
int n, q;
// 读入:数列个数、操作数、模数
cin >> n >> q >> mod;
// 读入原始数组
for (int i = 1; i <= n; i++)
cin >> a[i];
// 建树:根节点编号1,全局区间 [1, n]
build(1, 1, n);
while (q--)
{
int op;
cin >> op;
if (op == 1)
{
// 操作1:区间 [x,y] 乘 k
int x, y, k;
cin >> x >> y >> k;
update_mul(1, 1, n, x, y, k);
}
else if (op == 2)
{
// 操作2:区间 [x,y] 加 k
int x, y, k;
cin >> x >> y >> k;
update_add(1, 1, n, x, y, k);
}
else
{
// 操作3:查询区间 [x,y] 和并输出
int x, y;
cin >> x >> y;
cout << query(1, 1, n, x, y) << endl;
}
}
}
signed main()
{
// 关闭 cin/cout 同步,加速输入输出(竞赛必备)
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
while (T--) solve();
return 0;
}
模板二:区间整体取 min + 区间查最大值
例题:码蹄集OJ-磨刃定行期
代码如下:
cpp
#include<bits/stdc++.h>
using namespace std;
// 所有变量统一为 long long,避免大数溢出
#define int long long
#define endl '\n'
// 数组最大范围 2e5+10,满足题目 n,m ≤ 2e5
const int N = 2e5 + 10;
// 无穷大,作为线段树初始值、区间取min的基准
const int INF = 1e18;
// 线段树节点:存储对应区间的最大值
int max_val[N << 2];
// 懒标记:标记区间需要整体执行 取min 操作
int lazy[N << 2];
// 最终构造出来的答案数组
int a[N];
/**
* @brief 懒标记下传函数
* 把当前节点延迟的「区间取min」操作,下放给左右子节点
* @param u 当前线段树节点编号
* @param l,r 当前节点管辖的区间 [l, r]
*/
void pushdown(int u, int l, int r)
{
// 没有待执行的懒标记,直接返回
if (lazy[u] == INF) return;
int mid = (l + r) / 2;
// 左、右子节点编号
int ls = u << 1;
int rs = u << 1 | 1;
// 左子树更新:标记和数值都与当前懒标记取最小
lazy[ls] = min(lazy[ls], lazy[u]);
max_val[ls] = min(max_val[ls], lazy[u]);
// 右子树更新
lazy[rs] = min(lazy[rs], lazy[u]);
max_val[rs] = min(max_val[rs], lazy[u]);
// 清空当前节点懒标记,下传完成
lazy[u] = INF;
}
/**
* @brief 线段树建树
* 初始所有位置设为 INF(无穷大),后续通过区间取min不断缩小上界
* @param u 当前节点编号
* @param l,r 当前节点管辖区间
*/
void build(int u, int l, int r)
{
// 懒标记初始化为无穷大,表示无延迟操作
lazy[u] = INF;
// 叶子节点:单个位置,直接赋值 INF
if (l == r)
{
max_val[u] = INF;
return;
}
int mid = (l + r) / 2;
// 递归构建左子树、右子树
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
// 当前区间最大值 = 左右子树最大值的较大者
max_val[u] = max(max_val[u << 1], max_val[u << 1 | 1]);
}
/**
* @brief 区间更新:对 [L, R] 所有元素执行 a[x] = min(a[x], val)
* 作用:给区间设置上界,保证区间所有数 ≤ val
* @param u 当前节点编号
* @param l,r 当前节点区间
* @param L,R 目标修改区间
* @param val 用来取最小值的数值
*/
void update(int u, int l, int r, int L, int R, int val)
{
// 当前区间完全被目标区间包含,直接打懒标记并更新值
if (L <= l && r <= R)
{
lazy[u] = min(lazy[u], val);
max_val[u] = min(max_val[u], val);
return;
}
// 进入子树前必须先下传懒标记,保证数据正确
pushdown(u, l, r);
int mid = (l + r) / 2;
// 左区间有重叠,递归更新左子树
if (L <= mid) update(u << 1, l, mid, L, R, val);
// 右区间有重叠,递归更新右子树
if (R > mid) update(u << 1 | 1, mid + 1, r, L, R, val);
// 回溯更新当前节点的区间最大值
max_val[u] = max(max_val[u << 1], max_val[u << 1 | 1]);
}
/**
* @brief 区间查询:查询 [L, R] 区间的最大值
* 作用:校验每条约束是否满足「区间最大值恰好等于 v」
* @param u 当前节点编号
* @param l,r 当前节点区间
* @param L,R 目标查询区间
* @return 目标区间最大值
*/
int query(int u, int l, int r, int L, int R)
{
// 当前区间完全在查询范围内,直接返回该区间最大值
if (L <= l && r <= R)
return max_val[u];
// 下传懒标记
pushdown(u, l, r);
int mid = (l + r) / 2;
int res = -INF; // 最大值查询,初始设为负无穷
// 递归查询左右子树,取最大值
if (L <= mid) res = max(res, query(u << 1, l, mid, L, R));
if (R > mid) res = max(res, query(u << 1 | 1, mid + 1, r, L, R));
return res;
}
/**
* @brief 遍历线段树,把每个位置的值导出到答案数组 a
* @param u 当前节点编号
* @param l,r 当前节点区间
*/
void getArray(int u, int l, int r)
{
// 叶子节点:单个位置,赋值到答案数组
if (l == r)
{
// 该位置未被任何约束覆盖,按题目要求默认填 1
if (max_val[u] == INF)
a[l] = 1;
else
a[l] = max_val[u];
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
// 递归遍历左右子树
getArray(u << 1, l, mid);
getArray(u << 1 | 1, mid + 1, r);
}
// 单组测试数据逻辑
void solve()
{
int n, m;
cin >> n >> m;
// 初始化线段树
build(1, 1, n);
// 存储所有约束条件:l, r, v
vector<tuple<int, int, int>> cond;
for (int i = 0; i < m; i++)
{
int l, r, v;
cin >> l >> r >> v;
cond.emplace_back(l, r, v);
// 区间取min:给 [l,r] 设置上界 v
update(1, 1, n, l, r, v);
}
bool ok = true;
// 逐条校验所有约束
for (auto &t : cond)
{
int l = get<0>(t), r = get<1>(t), v = get<2>(t);
int now = query(1, 1, n, l, r);
// 两种无解情况:
// 1. 区间全是INF(无约束覆盖);2. 区间最大值不等于要求的v
if (now == INF || now != v)
{
ok = false;
break;
}
}
// 校验不通过,输出 No
if (!ok)
{
cout << "No\n";
return;
}
// 导出最终数组
getArray(1, 1, n);
cout << "Yes\n";
// 输出数组,规避行尾空格
for (int i = 1; i <= n; i++)
{
if (i > 1) cout << " ";
cout << a[i];
}
cout << endl;
}
signed main()
{
// 关闭cin/cout同步,加速输入输出(竞赛必备)
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
// 处理多组测试用例
while (T--) solve();
return 0;
}
模板三:区间加法 + 区间最大值查询+单点修改
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N = 2e5 + 10;
const int INF = 1e18;
int tree[N << 2]; // 线段树:存储区间最大值
int lazy[N << 2]; // 加法懒标记
int a[N]; // 原数组
// 懒标记下传
void pushdown(int u, int l, int r)
{
if (lazy[u] == 0) return;
// 下传至左右子节点
tree[u<<1] += lazy[u];
lazy[u<<1] += lazy[u];
tree[u<<1|1] += lazy[u];
lazy[u<<1|1] += lazy[u];
lazy[u] = 0; // 清空当前标记
}
// 建树
void build(int u, int l, int r)
{
lazy[u] = 0;
if (l == r)
{
tree[u] = a[l];
return;
}
int mid = (l + r) / 2;
build(u<<1, l, mid);
build(u<<1|1, mid + 1, r);
tree[u] = max(tree[u<<1], tree[u<<1|1]);
}
// 区间 [L,R] 整体加 val
void update_range(int u, int l, int r, int L, int R, int val)
{
if (L <= l && r <= R)
{
tree[u] += val;
lazy[u] += val;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update_range(u<<1, l, mid, L, R, val);
if (R > mid) update_range(u<<1|1, mid + 1, r, L, R, val);
tree[u] = max(tree[u<<1], tree[u<<1|1]);
}
// 单点修改:将 pos 位置赋值为 val
void update_point(int u, int l, int r, int pos, int val)
{
if (l == r)
{
tree[u] = val;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (pos <= mid) update_point(u<<1, l, mid, pos, val);
else update_point(u<<1|1, mid + 1, r, pos, val);
tree[u] = max(tree[u<<1], tree[u<<1|1]);
}
// 查询 [L,R] 区间最大值
int query_max(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return tree[u];
pushdown(u, l, r);
int mid = (l + r) / 2;
int res = -INF;
if (L <= mid) res = max(res, query_max(u<<1, l, mid, L, R));
if (R > mid) res = max(res, query_max(u<<1|1, mid + 1, r, L, R));
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (m--)
{
int op;
cin >> op;
if (op == 1)
{
// 区间加法
int l, r, v;
cin >> l >> r >> v;
update_range(1, 1, n, l, r, v);
}
else if (op == 2)
{
// 单点修改
int pos, v;
cin >> pos >> v;
update_point(1, 1, n, pos, v);
}
else
{
// 查询区间最大值
int l, r;
cin >> l >> r;
cout << query_max(1, 1, n, l, r) << endl;
}
}
return 0;
}
模板四:区间翻转+区间求和
cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N = 1e5 + 10;
int sum[N << 2]; // 区间亮灯数量
int lazy[N << 2]; // 翻转懒标记:1=需要翻转,0=无操作
// 懒标记下传
void pushdown(int u, int l, int r)
{
if (lazy[u] == 0) return;
int mid = (l + r) / 2;
int left = u << 1;
int right = u << 1 | 1;
// 左子树翻转:亮灯数 = 区间总长 - 原有亮灯数
sum[left] = (mid - l + 1) - sum[left];
lazy[left] ^= 1; // 标记异或1,翻转状态
// 右子树翻转
sum[right] = (r - mid) - sum[right];
lazy[right] ^= 1;
lazy[u] = 0; // 清空当前标记
}
// 建树:初始全关闭,sum=0
void build(int u, int l, int r)
{
lazy[u] = 0;
if (l == r)
{
sum[u] = 0;
return;
}
int mid = (l + r) / 2;
build(u<<1, l, mid);
build(u<<1|1, mid+1, r);
sum[u] = sum[u<<1] + sum[u<<1|1];
}
// 区间翻转 [L, R]
void update(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
{
sum[u] = (r - l + 1) - sum[u];
lazy[u] ^= 1;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update(u<<1, l, mid, L, R);
if (R > mid) update(u<<1|1, mid+1, r, L, R);
sum[u] = sum[u<<1] + sum[u<<1|1];
}
// 查询区间 [L, R] 亮灯数量
int query(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return sum[u];
pushdown(u, l, r);
int mid = (l + r) / 2;
int res = 0;
if (L <= mid) res += query(u<<1, l, mid, L, R);
if (R > mid) res += query(u<<1|1, mid+1, r, L, R);
return res;
}
void solve()
{
int n, m;
cin >> n >> m;
build(1, 1, n);
while (m--)
{
int c, a, b;
cin >> c >> a >> b;
if (c == 0)
{
// 区间翻转
update(1, 1, n, a, b);
}
else
{
// 查询亮灯数
cout << query(1, 1, n, a, b) << endl;
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
模板五:区间赋值
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int N = 1e6 + 10;
const int INF = 1e18;
int tree[N << 2]; // 区间最大值
int set_tag[N << 2]; // 赋值懒标记,INF 表示无赋值
int add_tag[N << 2]; // 加法懒标记
int a[N]; // 原数组
// 懒标记下传:先赋值,后加法
void pushdown(int u, int l, int r)
{
if (set_tag[u] == INF && add_tag[u] == 0)
return;
// 左子节点 u<<1
if (set_tag[u] != INF)
{
tree[u<<1] = set_tag[u];
set_tag[u<<1] = set_tag[u];
add_tag[u<<1] = 0;
}
tree[u<<1] += add_tag[u];
add_tag[u<<1] += add_tag[u];
// 右子节点 u<<1|1
if (set_tag[u] != INF)
{
tree[u<<1|1] = set_tag[u];
set_tag[u<<1|1] = set_tag[u];
add_tag[u<<1|1] = 0;
}
tree[u<<1|1] += add_tag[u];
add_tag[u<<1|1] += add_tag[u];
// 清空当前节点标记
set_tag[u] = INF;
add_tag[u] = 0;
}
// 建树
void build(int u, int l, int r)
{
set_tag[u] = INF;
add_tag[u] = 0;
if (l == r)
{
tree[u] = a[l];
return;
}
int mid = (l + r) / 2;
build(u<<1, l, mid);
build(u<<1|1, mid + 1, r);
tree[u] = max(tree[u<<1], tree[u<<1|1]);
}
// 区间赋值 [L,R] = val
void update_set(int u, int l, int r, int L, int R, int val)
{
if (L <= l && r <= R)
{
tree[u] = val;
set_tag[u] = val;
add_tag[u] = 0;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update_set(u<<1, l, mid, L, R, val);
if (R > mid) update_set(u<<1|1, mid + 1, r, L, R, val);
tree[u] = max(tree[u<<1], tree[u<<1|1]);
}
// 区间加法 [L,R] + val
void update_add(int u, int l, int r, int L, int R, int val)
{
if (L <= l && r <= R)
{
tree[u] += val;
add_tag[u] += val;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update_add(u<<1, l, mid, L, R, val);
if (R > mid) update_add(u<<1|1, mid + 1, r, L, R, val);
tree[u] = max(tree[u<<1], tree[u<<1|1]);
}
// 查询区间最大值
int query_max(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return tree[u];
pushdown(u, l, r);
int mid = (l + r) / 2;
int res = -INF;
if (L <= mid) res = max(res, query_max(u<<1, l, mid, L, R));
if (R > mid) res = max(res, query_max(u<<1|1, mid + 1, r, L, R));
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, 1, n);
while (q--)
{
int op;
cin >> op;
if (op == 1)
{
int l, r, x;
cin >> l >> r >> x;
update_set(1, 1, n, l, r, x);
}
else if (op == 2)
{
int l, r, x;
cin >> l >> r >> x;
update_add(1, 1, n, l, r, x);
}
else
{
int l, r;
cin >> l >> r;
cout << query_max(1, 1, n, l, r) << endl;
}
}
return 0;
}
模板六:区间加等差数列 + 单点查询
cpp
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll a[N];
int n, m;
// 线段树模板:区间加等差数列、单点查询
struct SegTree
{
ll tree[N << 2];
ll lazy[N << 2];
void pushdown(int u, int l, int r)
{
if (lazy[u] == 0) return;
tree[u<<1] += lazy[u];
lazy[u<<1] += lazy[u];
tree[u<<1|1] += lazy[u];
lazy[u<<1|1] += lazy[u];
lazy[u] = 0;
}
void build(int u, int l, int r)
{
lazy[u] = 0;
if (l == r)
{
tree[u] = 0;
return;
}
int mid = (l + r) / 2;
build(u<<1, l, mid);
build(u<<1|1, mid+1, r);
}
// 区间 [L,R] 加 val
void update(int u, int l, int r, int L, int R, ll val)
{
if (L <= l && r <= R)
{
tree[u] += val;
lazy[u] += val;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update(u<<1, l, mid, L, R, val);
if (R > mid) update(u<<1|1, mid+1, r, L, R, val);
}
// 单点查询 pos
ll query(int u, int l, int r, int pos)
{
if (l == r)
return tree[u];
pushdown(u, l, r);
int mid = (l + r) / 2;
if (pos <= mid) return query(u<<1, l, mid, pos);
else return query(u<<1|1, mid+1, r, pos);
}
}tr1, tr2;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
tr1.build(1, 1, n);
tr2.build(1, 1, n);
while (m--)
{
int opt;
cin >> opt;
if (opt == 1)
{
int l, r;
ll K, D;
cin >> l >> r >> K >> D;
ll v1 = K - 1LL * l * D;
ll v2 = D;
tr1.update(1, 1, n, l, r, v1);
tr2.update(1, 1, n, l, r, v2);
}
else
{
int p;
cin >> p;
ll add1 = tr1.query(1, 1, n, p);
ll add2 = tr2.query(1, 1, n, p);
ll res = a[p] + add1 + 1LL * p * add2;
cout << res << '\n';
}
}
return 0;
}
模板七:逆序对(单点更新 + 区间求和)
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
int a[N];
vector<int> all;
int n;
// 线段树:单点更新 + 区间求和
ll tree[N << 2];
// 建树
void build(int u, int l, int r)
{
if (l == r)
{
tree[u] = 0;
return;
}
int mid = (l + r) / 2;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tree[u] = tree[u << 1] + tree[u << 1 | 1];
}
// 单点位置 pos 加 1
void update(int u, int l, int r, int pos)
{
if (l == r)
{
tree[u]++;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) update(u << 1, l, mid, pos);
else update(u << 1 | 1, mid + 1, r, pos);
tree[u] = tree[u << 1] + tree[u << 1 | 1];
}
// 查询 [ql, qr] 区间和
ll query(int u, int l, int r, int ql, int qr)
{
if (qr < l || r < ql) return 0;
if (ql <= l && r <= qr) return tree[u];
int mid = (l + r) / 2;
return query(u << 1, l, mid, ql, qr) + query(u << 1 | 1, mid + 1, r, ql, qr);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
all.push_back(a[i]);
}
// 离散化:排序 + 去重
sort(all.begin(), all.end());
all.erase(unique(all.begin(), all.end()), all.end());
build(1, 1, n);
ll ans = 0;
// 从后往前遍历
for (int i = n; i >= 1; i--)
{
// 得到离散后的排名
int rk = lower_bound(all.begin(), all.end(), a[i]) - all.begin() + 1;
// 查询 [1, rk-1] 有多少数,即当前贡献的逆序对
ans += query(1, 1, n, 1, rk - 1);
// 当前数插入线段树
update(1, 1, n, rk);
}
cout << ans << endl;
return 0;
}
模板八:最长交替子串
题目:
给定一个长度为 n 的字符序列 a,初始时序列中全部都是字符 L。
有 q 次修改,每次给定一个 x,若 ax 为 L,则将 ax 修改成 R,否则将 ax 修改成 L。
对于一个只含字符 L,R 的字符串 s,若其中不存在连续的 L 和 R,则称 s 满足要求。
每次修改后,请输出当前序列 a 中最长的满足要求的连续子串的长度。
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;
// 线段树节点:维护区间左右字符、最长交替子串、前后缀最长交替长度
struct Node {
int l, r; // 区间左右边界
int lc, rc; // 区间左右端点字符 0:L 1:R
int maxlen; // 区间内最长合法交替子串长度
int lmax, rmax; // 左端最长前缀、右端最长后缀
} tr[N << 2];
int a[N]; // 原数组,0代表L,1代表R
int n, q;
// 由左右子节点向上更新当前节点信息
void pushup(int u) {
int ls = u << 1;
int rs = u << 1 | 1;
int lenL = tr[ls].r - tr[ls].l + 1;
int lenR = tr[rs].r - tr[rs].l + 1;
tr[u].lc = tr[ls].lc;
tr[u].rc = tr[rs].rc;
// 计算当前区间最长前缀
if (tr[ls].rmax == lenL && tr[ls].rc != tr[rs].lc)
tr[u].lmax = lenL + tr[rs].lmax;
else
tr[u].lmax = tr[ls].lmax;
// 计算当前区间最长后缀
if (tr[rs].lmax == lenR && tr[ls].rc != tr[rs].lc)
tr[u].rmax = lenR + tr[ls].rmax;
else
tr[u].rmax = tr[rs].rmax;
// 更新区间最大长度,考虑跨区间拼接
tr[u].maxlen = max(tr[ls].maxlen, tr[rs].maxlen);
if (tr[ls].rc != tr[rs].lc)
tr[u].maxlen = max(tr[u].maxlen, tr[ls].rmax + tr[rs].lmax);
}
// 线段树建树
void build(int u, int l, int r) {
tr[u].l = l;
tr[u].r = r;
// 叶子节点初始化
if (l == r) {
tr[u].lc = tr[u].rc = a[l];
tr[u].maxlen = tr[u].lmax = tr[u].rmax = 1;
return;
}
int mid = (l + r) / 2;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
// 单点修改:翻转指定位置字符
void update(int u, int pos) {
int l = tr[u].l, r = tr[u].r;
// 找到叶子,翻转字符
if (l == r) {
a[pos] ^= 1;
tr[u].lc = tr[u].rc = a[pos];
tr[u].maxlen = tr[u].lmax = tr[u].rmax = 1;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) update(u << 1, pos);
else update(u << 1 | 1, pos);
pushup(u);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
// 初始全部为 L(0)
for (int i = 1; i <= n; i++) a[i] = 0;
build(1, 1, n);
// 处理每次翻转操作并输出答案
while (q--) {
int x;
cin >> x;
update(1, x);
cout << tr[1].maxlen << '\n';
}
return 0;
}
模板九:三元上升子序列
题意:
在含有 n 个整数的序列 a1,a2,...,an 中,三个数被称作thair当且仅当 i<j<k 且 ai<aj<ak。
求一个序列中 thair 的个数。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5;//1<=a[i]<=1e5
// 线段树:区间计数,单点加,区间求和
struct SegTree {
int tree[MAXN << 2];
// 建树(初始全0)
void build(int u, int l, int r) {
tree[u] = 0;
if (l == r) return;
int mid = (l + r) / 2;
build(u<<1, l, mid);
build(u<<1|1, mid+1, r);
}
// 单点更新:位置x +1
void update(int u, int l, int r, int x) {
if (l == r) {
tree[u]++;
return;
}
int mid = (l + r) / 2;
if (x <= mid) update(u<<1, l, mid, x);
else update(u<<1|1, mid+1, r, x);
tree[u] = tree[u<<1] + tree[u<<1|1];
}
// 查询 [ql, qr] 区间和
int query(int u, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return 0;
if (ql <= l && r <= qr) return tree[u];
int mid = (l + r) / 2;
return query(u<<1, l, mid, ql, qr) + query(u<<1|1, mid+1, r, ql, qr);
}
} st;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n + 1), b;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
b.push_back(a[i]);
}
// 离散化
sort(b.begin(), b.end());
b.erase(unique(b.begin(), b.end()), b.end());
int len = b.size();
auto get_id = [&](int x) {
return lower_bound(b.begin(), b.end(), x) - b.begin() + 1;
};
vector<ll> L(n + 1, 0);
// 从左往右,求每个位置左边比它小的数个数
st.build(1, 1, len);
for (int j = 1; j <= n; ++j) {
int id = get_id(a[j]);
L[j] = st.query(1, 1, len, 1, id - 1);
st.update(1, 1, len, id);
}
vector<ll> R(n + 1, 0);
// 从右往左,求每个位置右边比它大的数个数
st.build(1, 1, len);
for (int j = n; j >= 1; --j) {
int id = get_id(a[j]);
R[j] = st.query(1, 1, len, id + 1, len);
st.update(1, 1, len, id);
}
// 统计总答案
ll ans = 0;
for (int j = 1; j <= n; ++j) {
ans += L[j] * R[j];
}
cout << ans << endl;
return 0;
}
模板十:带懒标记的区间覆盖(区间赋值)线段树,附加技巧:二进制位掩码统计集合种类
题意:题意概况
色板长度为 L,L 是一个正整数,所以我们可以均匀地将它划分成 L 块 1 厘米长的小方格。并从左到右标记为 1,2,...,L。
所有格子初始为颜色1,共 T 种颜色。
有 O 组操作:
-
C A B C :将区间 A,B 统一染为颜色 C(A>B 则交换);
-
P A B :查询区间 A,B 内有多少种不同颜色(A>B 则交换)。
利用颜色总数 T<=30 的特点,用区间覆盖线段树+二进制位掩码求解。
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int tree[N << 2];
int lazy[N << 2];
// 懒标记下传
void pushdown(int u, int l, int r)
{
if (!lazy[u]) return;
int mid = (l + r) / 2;
// 左孩子
tree[u << 1] = 1 << (lazy[u] - 1);
lazy[u << 1] = lazy[u];
// 右孩子
tree[u << 1 | 1] = 1 << (lazy[u] - 1);
lazy[u << 1 | 1] = lazy[u];
lazy[u] = 0;
}
// 建树,初始全为颜色1
void build(int u, int l, int r)
{
lazy[u] = 0;
if (l == r)
{
tree[u] = 1 << 0;
return;
}
int mid = (l + r) / 2;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
tree[u] = tree[u << 1] | tree[u << 1 | 1];
}
// 区间染色
void update(int u, int l, int r, int L, int R, int c)
{
if (L <= l && r <= R)
{
tree[u] = 1 << (c - 1);
lazy[u] = c;
return;
}
pushdown(u, l, r);
int mid = (l + r) / 2;
if (L <= mid) update(u << 1, l, mid, L, R, c);
if (R > mid) update(u << 1 | 1, mid + 1, r, L, R, c);
tree[u] = tree[u << 1] | tree[u << 1 | 1];
}
// 查询区间颜色掩码
int query(int u, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return tree[u];
pushdown(u, l, r);
int mid = (l + r) / 2;
int res = 0;
if (L <= mid) res |= query(u << 1, l, mid, L, R);
if (R > mid) res |= query(u << 1 | 1, mid + 1, r, L, R);
return res;
}
// 统计二进制中1的个数(颜色种类)
int countBit(int x)
{
int cnt = 0;
while (x)
{
cnt += x & 1;
x >>= 1;
}
return cnt;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int L, T, O;
cin >> L >> T >> O;
build(1, 1, L);
while (O--)
{
char op;
cin >> op;
if (op == 'C')
{
int a, b, c;
cin >> a >> b >> c;
if (a > b) swap(a, b);
update(1, 1, L, a, b, c);
}
else if (op == 'P')
{
int a, b;
cin >> a >> b;
if (a > b) swap(a, b);
int mask = query(1, 1, L, a, b);
cout << countBit(mask) << '\n';
}
}
return 0;
}