线段树模板

模板一:区间加法 + 区间求和(取模) + 区间乘法

例题: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

对于一个只含字符 LR 的字符串 s,若其中不存在连续的 LR,则称 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 组操作:

  1. C A B C :将区间 A,B 统一染为颜色 C(A>B 则交换);

  2. 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;
}
相关推荐
段一凡-华北理工大学2 小时前
2026 高炉炼铁智能化技术全景与演进路径~系列文章11:演进路径与行业未来
大数据·网络·人工智能·算法·工业智能体·高炉炼铁智能化
叶小鸡2 小时前
小鸡玩算法-力扣HOT100-多维动态规划
算法·leetcode·动态规划
星马梦缘2 小时前
aaaaa
数据结构·c++·算法
菜菜的顾清寒3 小时前
力扣HOT100(42)链表-随机链表的复制
算法·leetcode·链表
lqqjuly3 小时前
模型剪枝与稀疏化:理论、算法与可运行实现
人工智能·算法·剪枝
逻辑君3 小时前
Foresight研究报告【20260011】
人工智能·线性代数·算法·矩阵
珊瑚里的鱼3 小时前
【动态规划】不同路径Ⅱ
算法·动态规划
适应规律4 小时前
【无标题】
人工智能·python·算法
蒟蒻的贤4 小时前
关于文法G2算符优先分析的一个坑
算法