线段树

===

Index

简介

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为 O(log(n))。而未优化的空间复杂度为2N ,因此有时需要离散化让空间压缩。

用于 幺半群 的数据结构, (S, .: S x S -> S, e : S),满足

  • 分配律:(a · b )· c = a · (b · c)
  • 存在单位元素,a⋅e = e⋅a = a

主要用途

在O(log N)时间复杂度内实现:

  • 单点修改
  • 区间修改
  • 区间查询,如区间求和,求区间最大值、最小值等
  • 区间修改

线段树维护的信息,需要满足可加性,且要以可以接受的速度合并信息和修改信息,如果使用标记,标记也要满足可加性(例如取模就不满足可加性,对 4取模然后对 3 取模,两个操作就不能合并在一起做(事实上某些情况下可以暴力单点取模)

区间修改与懒标记

区间修改是个很有趣的东西……你想啊,如果你要修改区间 ,难道把所有包含在区间[l,r]中的节点都遍历一次、修改一次?那估计这时间复杂度估计会上天。这怎么办呢? 我们这里要引用一个叫做 懒惰标记 的东西。

模板代码

#include <vector>
#include <cassert>
using namespace std;

template <class S, S (*op)(S, S), S (*e)()>
struct segtree {
public:
    segtree() : segtree(0) {}
    explicit segtree(int n) : segtree(std::vector<S>(n, e())) {}
    explicit segtree(const std::vector<S>& v) : _n(int(v.size())) {
        log = ceil_pow2(_n);
        size = 1 << log;
        d = std::vector<S>(2 * size, e());
        for (int i = 0; i < _n; i++) d[size + i] = v[i];
        for (int i = size - 1; i >= 1; i--) {
            update(i);
        }
    }

    // @param n `0 <= n`
    // @return minimum non-negative `x` s.t. `n <= 2**x`
    int ceil_pow2(int n) {
        int x = 0;
        while ((1U << x) < (unsigned int)(n)) x++;
        return x;
    }

    void set(int p, S x) {
        assert(0 <= p && p < _n);
        p += size;
        d[p] = x;
        for (int i = 1; i <= log; i++) update(p >> i);
    }

    S get(int p) const {
        assert(0 <= p && p < _n);
        return d[p + size];
    }

    S prod(int l, int r) const {
        assert(0 <= l && l <= r && r <= _n);
        S sml = e(), smr = e();
        l += size;
        r += size;

        while (l < r) {
            if (l & 1) sml = op(sml, d[l++]);
            if (r & 1) smr = op(d[--r], smr);
            l >>= 1;
            r >>= 1;
        }
        return op(sml, smr);
    }

    S all_prod() const { return d[1]; }

    template <bool (*f)(S)> int max_right(int l) const {
        return max_right(l, [](S x) { return f(x); });
    }
    template <class F> int max_right(int l, F f) const {
        assert(0 <= l && l <= _n);
        assert(f(e()));
        if (l == _n) return _n;
        l += size;
        S sm = e();
        do {
            while (l % 2 == 0) l >>= 1;
            if (!f(op(sm, d[l]))) {
                while (l < size) {
                    l = (2 * l);
                    if (f(op(sm, d[l]))) {
                        sm = op(sm, d[l]);
                        l++;
                    }
                }
                return l - size;
            }
            sm = op(sm, d[l]);
            l++;
        } while ((l & -l) != l);
        return _n;
    }

    template <bool (*f)(S)> int min_left(int r) const {
        return min_left(r, [](S x) { return f(x); });
    }
    template <class F> int min_left(int r, F f) const {
        assert(0 <= r && r <= _n);
        assert(f(e()));
        if (r == 0) return 0;
        r += size;
        S sm = e();
        do {
            r--;
            while (r > 1 && (r % 2)) r >>= 1;
            if (!f(op(d[r], sm))) {
                while (r < size) {
                    r = (2 * r + 1);
                    if (f(op(d[r], sm))) {
                        sm = op(d[r], sm);
                        r--;
                    }
                }
                return r + 1 - size;
            }
            sm = op(d[r], sm);
        } while ((r & -r) != r);
        return 0;
    }

private:
    int _n, size, log;
    std::vector<S> d;

    void update(int k) { d[k] = op(d[2 * k], d[2 * k + 1]); }
};

使用方法

1.构造函数

segtree<S, op, e> seg(int n)
segtree<S, op, e> seg(vector<S> v)

需要定义

  • S 类型
  • S op(S a, S, b) 二元操作
  • S e() 单位元素

例如,对于整型类型的区间最小值,定义方式如下

int op(int a, int b){
    return min(a, b);
}

int e() {
    //INF ,满足 op(e(), a) = op(a, e()) = a,  min(a, e()) = a
    return (int)1e9; 
}

segtree<int, op, e> seg(10); // 初始化长度为10数组,每个1元素都是 e()
  • 约束: 0 <= n <= 1e8
  • 时间复杂度 O(n)
  1. set()函数

将 x 赋值给 a[p]

void seg.set(int p, int x)
  • 约束: 0 <= p < n
  • 时间复杂度 O(logn)

3.get函数

返回元素 a[p]

S seg.get(int p)
  • 约束: 0 <= p < n
  • 时间复杂度 O(1)

3.prod函数

如果 l=r, 返回 e(), 否则 返回 op(a[l], a[l+1], … , a[r - 1])

S seg.prod(int l, int r)
  • 约束: 0 <= l <= r <= n
  • 时间复杂度 O(log(n))

4.all_prod函数

如果 n=0, 返回 e(), 否则 返回 op(a[0], a[1], … , a[n - 1])

S seg.all_prod()
  • 时间复杂度 O(1)

5.max_right函数

  1. 需要定义 bool f(S x) 函数,max_right函数在线段树上执行二分查找
  2. 应该定义以S为参数并返回bool的函数对象。

函数返回一个下标 r, 满足下面两个条件

  • r = l or f(op(a[l], a[l+1], …, a[r-1])) = true
  • r = n or f(op(a[l], a[l+1], …, a[r])) = false

如果 f 是单调的, 返回 满足 f(op(a[l], a[l + 1], ..., a[r - 1])) = true 的最大的 r

1. int seg.max_right<f>(int l)
2. int seg.max_right<F>(int l, F f)
  • 0 <= l <= n
  • f(e()) = true
  • f函数传入相同的参数时,返回值相同
  • 时间复杂度 O(logn)
  1. min_left
(1) int seg.min_left<f>(int r)
(2) int seg.min_left<F>(int r, F f)
  1. 需要定义 bool f(S x) 函数,max_right函数在线段树上执行二分查找
  2. 应该定义以S为参数并返回bool的函数对象。

函数返回一个下标 l, 满足下面两个条件

  • l = r or f(op(a[l], a[l + 1], …, a[r - 1])) = true
  • l = 0 or f(op(a[l - 1], a[l], …, a[r - 1])) = false

如果 f 是单调的, 返回 满足 f(op(a[l], a[l + 1], ..., a[r - 1])) = true 的最小的 l

  • 0 <= l <= n
  • f(e()) = true
  • f函数传入相同的参数时,返回值相同
  • 时间复杂度 O(logn)

不同信息的函数设置

1.最大值

int op(int a, int b){
    return max(a, b);
}

int e() {
    return -1; // 比数组中最小元素要小 即满足 max(e(),a[i]) = a[i]
}

2.最小值

int op(int a, int b){
    return min(a, b);
}

int e() {
    //INF ,满足 op(e(), a) = op(a, e()) = a,  min(a, e()) = a
    return (int)1e9; 
}

3.区间和

int op(int a, int b){
    return a+b;
}

int e() {
    return 0;  // e() + a[i] = a[i]
}

//将第i个数+b,
seg.seg(a-1, seg.get(a-1) + b);

单点修改例题

区间取模

cf 438D

给定一个序列(长度n)和m次操作,每次操作有三种形式:

  1. 计算 a[l,r]的和
  2. 对 l<=i<=r, 执行 a[i] = a[i] % x
  3. 赋值 a[k] = x
  • 1 <= n, m <= 1e5
  • 1 <= a[i] <= 1e9

分析

取模的性质 a[i] % x <= a[i] / 2, 即每执行一个取模操作,数值至少会减为原来的1半,n个数最多会执行nlog(n)次操作。维护区间最大值,最大值坐标和区间和,暴力模拟即可。

struct S {
    ll sum;
    int mx, pos;
    S(): sum(0),mx(-1),pos(-1){}
    S(ll a, int b, int c) :sum(a), mx(b), pos(c){}
};
 
S op(S x, S y) {
    S s;
    s.sum = x.sum + y.sum;
    s.mx = max(x.mx, y.mx);
    s.pos = x.mx > y.mx ? x.pos : y.pos;
    return s;
}
S e() {
    return S();
}
int main() {    
    int n, q, x;
    cin >> n >> q;
    vector<S> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> x;
        a[i] = S{x, x, i};
    }
    SegTree<S, op, e> seg(a);
    for (int i = 0, t, l, r, p; i < q; ++i) {
        cin >> t;
        if (t == 1) {
            cin >> l >> r;
            l--;
            cout << seg.get(l, r).sum << '\n';
        } else if (t == 2){
            cin >> l >> r >> p;
            l--;
            auto s = seg.get(l, r);
            while (s.mx >= p) {
                seg.set(s.pos, S{s.mx % p, s.mx % p, s.pos});
                s = seg.get(l, r);
            }
        } else {
            cin >> p >> x;
            p--;
            seg.set(p, S{x, x, p});
        }
    }
    return 0;
}

区间加求区间gcd

acwing 246

给定长度为n的数组a和m次操作,每次操作有如下两种形式:

  • C l r d 把 a[l],..a[r] 都加上d
  • Q l r 输出 a[l],..,a[r]的最大公约数

  • 1 <= n <= 5e5
  • 1 <= m <= 1e5
  • 1 <= a[i], d <= 1e18

分析

gcd(a[l],...a[r]) = gcd(a[l], a[l+1]-a[l], ..., a[r] - a[r - 1])

设b为a的差分数组,则a数组区间加,可以转化为b数组两点加,b[l]+d, b[r+1]-d. 操作2:

gcd(a[l],a[l+1],..,a[r]) = gcd(a[l],a[l+1]-a[l], ..., a[r] - a[r - 1])
                         = gcd(a[l], gcd(b[l+1],b[l+2],...b[r]))
                         = gcd(b[1]+..+b[l], gcd(b[l+1],b[l+2],...b[r]))

所以,只需用线段树维护差分数组的区间和以及区间gcd即可。

struct S{
    long long s,d; //差分数目区间和,区间GCD
    S& operator + (const long long x) {
        s += x, d += x;
        return *this;
    }
};
S op(S x, S y) {
    return S{x.s + y.s,gcd(x.d, y.d)};
}
S e() {
    return S{0,0};
}
void ac_yyf(int tt) {
    cin >> n >> m;
    vector<long long> a(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    vector<S> v(n + 1);
    for (int i = 0; i < n; ++i) {
        v[i] = S{a[i + 1] - a[i], a[i + 1] - a[i]};
    }
    SegTree<S, op, e> seg(v);

    char c;
    long long d;
    for (int i = 0, l, r; i < m; ++i) {
        cin >> c >> l >> r;
        if (c == 'C') {
            cin >> d;
            l--;
            seg.set(l, seg.get(l) + d);
            seg.set(r, seg.get(r) + (-d));
        } else {
            cout << gcd(seg.get(0, l).s, seg.get(l, r).d) << '\n';
        }
    }
}

维护区间最值及出现次数

step1 c

给定长度为n的数组a和m次操作,每次操作有如下两种形式:

  • 1 i v 赋值 a[i] = v. (0 <= i < n, 0 <= v <= 1e9)
  • 2 l r 输出区间[l, r]中的最小值,以及区间中有多少个数等于最小值。

  • 1 <= n, m <= 1e5
  • 1 <= a[i] <= 1e9

分析

线段树维护两个值,一个区间最值,及出现次数。

#include <bits/stdc++.h>
using namespace std;
struct SegTree {
    ;// ...
};
struct S {
    int mn, cnt;
    S():mn(1e9), cnt(0) {}
    S(int m, int c): mn(m), cnt(c) {}
};

S op(S x, S y) {
    S res;
    int m1 = x.mn, c1 = x.cnt, m2 = y.mn, c2 = y.cnt;
    if (m1 < m2) {
        res = x;
    } else if (m1 > m2) {
        res = y;
    } else res = S(m1, c1 + c2);
    return res;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    vector<S> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i].mn;
        a[i].cnt = 1;   
    }
    SegTree<S, op, e> seg(a);
    for (int i = 0, op, x, y; i < q; ++i) {
        cin >> op >> x >> y;
        if (op == 1) {
            seg.set(x, S{y, 1});
        } else {
            S s = seg.get(x, y);
            cout << s.mn << ' ' << s.cnt << '\n';
        }
    }
    return 0;
}

维护区间最大子数组和

setp2 a

给定长度为n的数组a和m次(1 <= n, m <= 1e5)操作,每次操作给定i, v, 赋值a[i]=v. (0 <= i < n, -1e9 <= a[i], v <= 1e9).

输出m+1行,初始最大子数组和以及每次操作后的最大子数组和。

struct S {
    long long max_sum;
    long long all_sum;
    long long left_suf;
    long long right_pref;
    S(): max_sum(0), all_sum(0), left_suf(0), right_pref(0) {}
    S(long long ms, long long as, long long ls, long long rp) :
    max_sum(ms),all_sum(as),left_suf(ls),right_pref(rp) {}
};

S op(S x, S y) {
    S res;
    res.all_sum = x.all_sum + y.all_sum;
    res.left_suf = max(x.left_suf, x.all_sum + y.left_suf);
    res.right_pref = max(y.right_pref, y.all_sum + x.right_pref);
    res.max_sum = max({x.max_sum, y.max_sum, x.right_pref + y.left_suf});
    return res;
}

S e() {
    return S();
}
int main() {
    int n, q;
    cin >> n >> q;
    vector<S> a(n);
    long long x;
    for (int i = 0; i < n; ++i) {
        cin >> x;
        a[i] = S{x, x, x, x}; 
    }
    SegTree<S,op,e> seg(a);
    long long mx = max(0ll, seg.get_all().max_sum);
    cout << mx << "\n";
    for (int i = 0, op, x, y; i < q; ++i) {
        cin >> x >> y;
        seg.set(x, S{y, y,y,y});
        long long mx = max(0ll, seg.get_all().max_sum);
        cout << mx << "\n";
    }
}

第k个1的下标

step2 B

给定长度为n的数组a(a[i] = 0或1)和m次(1 <= n, m <= 1e5)操作,每次操作有如下两种形式:

  • 1 i 赋值 a[i] = 1 - a[i];
  • 2 k 输出第k个1的下标(保证k不超过1的数量,下标从0开始)

分析

线段树二分

using S = int;
S op(S x, S y) {
    S s = x + y;
    return s;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);

    int n, q;
    cin >> n >> q;
    vector<S> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    SegTree<S, op, e> seg(a);
    for (int i = 0, op, x, target; i < q; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> x;
            seg.set(x, 1 - seg.get(x));
        } else {
            cin >> target;
            int p = seg.max_right(0, [&](int x) {
                return x < target + 1;
            });
            cout << p << "\n";
        }
    }
    return 0;
}

根据逆序对恢复原始排列

step3 b

对于长度为n(1<=n<=1e5)的排列p,给定数组a, a[i]表示 j<ip[j]>p[i]的数量,求最初排列。

分析

初始每个位置都是1,从右往左计算,每次从右往左二分,找到一个位置i,满足i右边有a[i]个1(表示有a[i]个比当前元素大的数还未使用). 然后将该位置置0,表示该数已使用。

using S = int;
S op(S x, S y) {
    S s = x + y;
    return s;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);

    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];   
    }
    SegTree<S,op,e> seg(vector<int>(n, 1));
    vector<int> ans(n);
    for (int i = n - 1; i >= 0; i--) {
        int x =  seg.min_left(n, [&](int x) {
            return x < a[i] + 1;
        });
        seg.set(x - 1, 0);
        ans[i] = x;
    }
    for (int i = 0; i < n; ++i) {
        cout << ans[i] << " \n"[i == n];
    }
}

求嵌入区间的数目

step3 c

长度为2*n, 1 <= n <= 1e5的数组,1-n中每个数各出现2次,如果数字y的两次出现都在数字x的区间内,则称y是x的嵌入区间,对于1-n,分别求其嵌入区间的数目。

分析

遇到右边界时,将区间的左边界在线段树上的位置设为1,同时 统计坐标在左右边界之间出现的1的数目

using S = int;
int op(int x, int y) {
    return x + y;
}
int e(){
    return 0;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n;
    cin >> n;
    SegTree<S,op,e> seg(n*2);
    vector<int> a(n * 2), pos(n, -1), ans(n);
    for (int i = 0; i < n * 2; ++i) {
        cin >> a[i];
        a[i]--;
        if (pos[a[i]] == -1) pos[a[i]] = i;
        else {
            ans[a[i]] = seg.get(pos[a[i]], i + 1);
            seg.set(pos[a[i]], 1);
        }
    }
    for (int i = 0; i < n; ++i) {
        cout << ans[i] << " \n"[i == n - 1];
    }
}

求交叉区间的数目

step3 d

长度为2*n, 1 <= n <= 1e5的数组,1-n中每个数各出现2次,如果数字y的出现和数字x的出现位置存在交叉,则称y是x的交叉区间,对于1-n,分别求其嵌入区间的数目。

分析

交叉区间, ` * x * . x . ` 包括关于左边界的交叉和右边界的交叉,两者可以分别计算,以右边界交叉为例,

第一次遇到数字x时,将其位置置1,第二次遇到x时,统计两个x之间1的数目,同时将左边界置0.

对于左边界交叉,从后往前,相同方法再计算一遍即可。

using S = int;

int op(int x, int y) {
    return x + y;
}

int e(){
    return 0;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n;
    cin >> n;
    SegTree<S,op,e> seg(n*2);
    vector<int> a(n * 2), pos(n, -1), ans(n);
    for (int i = 0; i < n * 2; ++i) {
        cin >> a[i];
        a[i]--;
        if (pos[a[i]] == -1) {
            pos[a[i]] = i;
            seg.set(pos[a[i]], 1);
        }
        else {
            ans[a[i]] = seg.get(pos[a[i]] + 1, i);
            seg.set(pos[a[i]], 0);
        }
    }
    pos = vector<int>(n, -1);
    for (int i = 2 * n - 1; i >= 0; --i) {
        if (pos[a[i]] == -1) {
            pos[a[i]] = i;
            seg.set(pos[a[i]], 1);
        }
        else {
            ans[a[i]] += seg.get(i, pos[a[i]]);
            seg.set(pos[a[i]], 0);
        }
    }
    for (int i = 0; i < n; ++i) {
        cout << ans[i] << " \n"[i == n - 1];
    }
}

区间交替符号和

step4 a

长度为n的数组a和q次操作每次操作有两种:

  1. 0, i, j 赋值 a[i] = j
  2. 1, l, r 计算 a[l] - a[l+1]+a[l+2]-...+/-a[r]
  • 1 <= n, q <= 1e5
  • 1 <= i <= n, 1 <= j <= 1e4
  • 1 <= l <= r <= n

分析

可以用两个线段树,分别维护奇偶下标的和。

using S = int;
S op(S x, S y) {
    S s = x + y;
    return s;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n ,q;
    cin >> n;
    vector<int> a(n), b(n);
    for (int i = 0; i < n; ++i) {
        if (i % 2 == 0) cin >> a[i];
        else cin >> b[i];
    }

    SegTree<S, op, e> s1(a);
    SegTree<S, op, e> s2(b);
    cin >> q;
    while (q--) {
        int op,x,y;
        cin>>op>>x>>y;
        if(op==0){
            x--;
            if(x%2==0) s1.set(x,y);
            else s2.set(x,y);
        }else{
            x--;
            auto u = s1.get(x, y), v = s2.get(x, y);
            cout << (x % 2 == 0 ? u - v : v - u) << '\n';
        }
    }
    return 0;
}

区间逆序对

step4 c

长度为n的数组a 1<=a[i]<=40, q个查询,每个查询有两种:

  1. 1 x, y 求区间[x,y]中的逆序对数目, (1<=x<=y<=n)
  2. 2 x y 赋值 a[x] = y (1<=x<=n, 1<=y<=40)

分析

数据范围较小,直接维护每个数的频率,合并时统计逆序对。

struct S{
    vector<int> freq;
    long long cnt;
    S():cnt(0),freq(40){}
};
S op(S x, S y) {
    S s;
    for (int i = 0; i < 40; ++i) {
        s.freq[i] = x.freq[i] + y.freq[i];
    }
    long long cnt = x.cnt + y.cnt;
    for (int i = 0; i < 40; ++i) {
        for (int j = i + 1; j < 40; ++j) {
            cnt += y.freq[i] * x.freq[j];
        }
    }
    s.cnt = cnt;
    return s;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, q, x;
    cin >> n >> q;
    vector<S> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> x;
        x--;
        a[i].freq[x] = 1;   
    }
    SegTree<S, op, e> seg(a);
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int l, r;
            cin >> l >> r; l--;
            cout << seg.get(l, r).cnt << '\n';
        } else {
            int i, v;
            cin >> i >> v; i--;v--;
            S s; s.freq[v] = 1;
            seg.set(i, s);
        }
    }
    return 0;
}

区间非下降子数组数目

no inversioins

给定长度为n的字符串s,仅包含小写字母,q次询问,每次询问给定[l,r],求区间[l,r]中优秀子串的数目,如果一个字符串不存在逆序对,则称优秀子串。

  • 1 <= n, q <= 2e5
  • l <= l <= r <= n
struct S {
    long long cnt; //总数目
    int lcnt, rcnt; // 从左边/右边非递减最大长度
    char left, right;  // 最左边和最右边字符
    bool isGood;
    S():cnt(0),lcnt(0),rcnt(0),left(0),right(0),isGood(true){}
    S(int u){
        cnt=lcnt=rcnt=(u>0);
        left=right=u; isGood=true;
    }
};
 
S op(S x, S y) {
    S s;
    s.cnt = x.cnt + y.cnt;
    if (x.right <= y.left) {
        s.cnt += x.rcnt * 1ll * y.lcnt;
        s.isGood = x.isGood & y.isGood;
        s.lcnt = x.isGood ? y.lcnt + x.lcnt : x.lcnt;
        s.rcnt = y.isGood ? x.rcnt + y.rcnt : y.rcnt;
    } else {
        s.isGood = false;
        s.lcnt = x.lcnt;
        s.rcnt = y.rcnt;
    }
    s.left = x.left;
    s.right = y.right;
    return s;
}
S e() {
    return S{};
}
void ac_yyf(int tt) {
    rd(n,s,q);
    SegTree<S, op, e> seg(n);
    for (int i = 0; i < n; ++i) {
        seg.set(i, s[i]-'a'+1);
    }
    while(q--){
        rd(x,y);
        cout<<seg.get(x-1,y).cnt<<nl;
    }
}

区间方差

洛谷 p5142

给定一个长为n的数组吗,m次查询。

  • 1 l r 赋值 a[l] = r
  • 2 l r 求区间 [l,r]的方差, 模1e9+7

  • 1 <= n,m <= 1e5
  • 1 <= a[i], y <= 1e9
using S = mint;
S op(S x, S y) {
    return x + y;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    vector<mint> a(n), b(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        b[i] = a[i] * a[i];
    }

    SegTree<S, op, e> s1(a), s2(b);

    for (int i = 0, t, x, y; i < m; ++i) {
        cin >> t >> x >> y;
        x--;
        if (t == 1) {
            s1.set(x, y);
            s2.set(x, mint(y) * y);
        } else {
            mint c = s1.get(x, y) / (y - x);
            mint ans =s2.get(x, y) / (y - x) - c * c;
            cout << ans << '\n';
        }
    }
    return 0;
}

带懒标记例题

区间取max

part2 step1B

n个初始为0元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] = max(a[i], v) (0 <= v, a[i] <= 1e9)
  • 2 i 输出第i个元素
struct S {
    int mx;
    int size;
};
using F = int;
S op(S x, S y) {
    S s;
    s = S{max(x.mx, y.mx), x.size + y.size};
    return s;
}
S e() {
    return S();
};
S tag(F f, S s) { 
    S res;
    res.mx = max(f, s.mx);
    res.size = s.size;
    return res;
}
F merge(F x, F y) { return max(x, y); }
F id() { return 0; }

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,1}));
    for (int i = 0, op, l, r, v; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> v;
            seg.apply(l, r, v);
        } else {
            cin >> v;
            cout << seg.get(v).mx << '\n';
        }
    }
    return 0;
}

区间赋值

part2 step1C

n个初始为0元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] = v (0 <= v <= 1e9)
  • 2 i 输出第i个元素
struct S {
    long long sum;
    int size;
};
using F = long long;
S op(S x, S y) {
    S s;
    s = S{x.sum + y.sum, x.size + y.size};
    return s;
}
S e() {
    return S();
};
S tag(F f, S s) { 
    S res;
    res.size = s.size;
    res.sum = f == -1 ? s.sum : f * s.size;
    return res;
}
F merge(F x, F y) { 
    return x == -1 ? y : x;
}
F id() { return -1; }  // -1表示无懒标记

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,1}));
    for (int i = 0, op, l, r, v; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> v;
            seg.apply(l, r, v);
        } else {
            cin >> v;
            cout << seg.get(v).sum << '\n';
        }
    }

    return 0;
}

区间加区间求min

part2 step2A

n个初始为0元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] += v (0 <= v <= 1e9)
  • 2 l r 输出min(a[l], a[l+1], …a[r-1])
struct S {
    long long sum, mn;
    int size;
    S():sum(0),mn(1e18),size(0){} // mn 默认取最大值
    S(long long s, long long m, int siz):sum(s),mn(m),size(siz){}
};
using F = long long;
S op(S x, S y) {
    return S{x.sum + y.sum, min(x.mn, y.mn), x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return S{s.sum + f * s.size, s.mn + f, s.size};
}
F merge(F x, F y) { 
    return x + y;
}
F id() { return 0; }  

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,0,1}));
    for (int i = 0, op, l, r, v; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> v;
            seg.apply(l, r, v);
        } else {
            cin >> l >> r;
            cout << seg.get(l, r).mn << '\n';
        }
    }
    return 0;
}

区间乘与区间和

part2 step2B

n个初始为1元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] *= v (1 <= v <= 1e9+7)
  • 2 l r 输出sum(a[l…r-1])的和模1e9+7
struct S {
    mint s;
    int size;
    S():s(0),size(0){}
    S(mint _s, int siz):s(_s),size(siz){}
};
using F = mint;
S op(S x, S y) {
    return S{x.s + y.s, x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return S{s.s * f, s.size};
}
F merge(F x, F y) { 
    return x * y;
}
F id() { return 1; } 

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{1,1}));
    for (int i = 0, op, l, r, v; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> v;
            seg.apply(l, r, v);
        } else {
            cin >> l >> r;
            cout << seg.get(l, r).s << '\n';
        }
    }
    return 0;
}

区间or与区间and

part2 step2C

n个初始为0元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] |= v (1 <= v <= 2^30)
  • 2 l r 输出AND(a[l…r-1])的和
struct S {
    int sum;
    int size;
    S():sum(((1<<30)-1)),size(0){} // and e()默认值需要为全为1的数。
    S(int s, int siz):sum(s),size(siz){}
};
using F = int;
S op(S x, S y) {
    return S{x.sum & y.sum, x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return S{s.sum | f, s.size};
}
F merge(F x, F y) { 
    return x | y;
}
F id() { return 0; } 

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,1}));
    for (int i = 0, op, l, r, v; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> v;
            seg.apply(l, r, v);
        } else {
            cin >> l >> r;
            cout << seg.get(l, r).sum << '\n';
        }
    }
    return 0;
}

区间赋值区间min

part2 step2E

n个初始为0元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] = v (1 <= v <= 1e9)
  • 2 l r 输出min(a[l…r-1])
struct S {
    long long sum, mn;
    S():sum(0),mn(1e18){} // mn初始化1e18,这里可以不用维护size
    S(long long s, long long m):sum(s),mn(m){}
};
using F = long long;
S op(S x, S y) {
    return S{x.sum + y.sum, min(x.mn, y.mn)};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return f == -1 ? S{s.sum, s.mn} : S(f, f);
}
F merge(F x, F y) { 
    return x == -1 ? y : x;
}
F id() { return -1; }  // -1: no tag
LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,0}));

区间赋值区间和

part2 step2F

n个初始为0元素,m次操作。(1<=n,m<=1e5)

  • 1 l r v : 对所有 l <= i < r 执行 a[i] = v (1 <= v <= 1e9)
  • 2 l r 输出sum(a[l…r-1])
struct S {
    long long sum;
    int size;
    S():sum(0),size(0){}
    S(long long s, int siz):sum(s),size(siz){}
};
using F = long long;
S op(S x, S y) {
    return S{x.sum + y.sum, x.size+y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return f == -1 ? S{s.sum, s.size} : S{f * s.size, s.size};
}
F merge(F x, F y) { 
    return x == -1 ? y : x;
}
F id() { return -1; }  // -1: no tag
LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,1}));

区间赋值区间最大子数组

part2 step3A

n个初始为0元素,m次操作。(1<=n,m<=1e5),每次操作

  • l r v : 对所有 l <= i < r 执行 a[i] = v (-1e9 <= v <= 1e9)

每次操作输出最大子数组和。

struct S {
    long long max_sum, sum, max_pre, max_suf;
    int size;
    S(): max_sum(0), sum(0), max_pre(0), max_suf(0), size(0){}
    S(long long ms, long long s, long long pre, long long suf, int siz) :
    max_sum(ms),sum(s),max_pre(pre),max_suf(suf), size(siz){}
};

S op(S x, S y) {
    S res;
    res.sum = x.sum + y.sum;
    res.size = x.size + y.size;
    res.max_pre = max(x.max_pre, x.sum + y.max_pre);
    res.max_suf = max(y.max_suf, y.sum + x.max_suf);
    res.max_sum = max({x.max_sum, y.max_sum, x.max_suf + y.max_pre});
    return res;
}
const long long  inf = 1e18;
using F = long long;
S e() {
    return S();
};
S tag(F f, S s) { 
    if (f == inf) return s;
    long long p = f * s.size;
    return f > 0 ? S{p, p, p, p, s.size} : S{0, p, 0, 0, s.size};
}
F merge(F x, F y) { 
    return x == inf ? y : x;
}
F id() { return inf; }  // v的范围在[-1e9,1e9],需要找一个区间外的数作为无懒标记标志

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,0,0,0,1}));
    for (int i = 0, l, r, v; i < m; ++i) {
        cin >> l >> r >> v;
        seg.apply(l, r, v);
        cout << seg.get_all().max_sum << '\n';
    }
    return 0;
}

区间异或区间和

part2 step3B

n个初始为0元素,m次操作。(1<=n,m<=1e5),每次操作

  • 1 l r : 对所有 l <= i < r 执行 a[i] ^ 1
  • 2 k : 输出第k个1下标 (k从0开始,下标从0开始)
struct S {
    int sum, size;
    S():sum(0),size(0){}
    S(int s, int siz): sum(s), size(siz){}
};
using F = int;
S op(S x, S y) {
    return S{x.sum + y.sum, x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return f == 0 ? s : S{s.size - s.sum, s.size};
}
F merge(F x, F y) { 
    return x ^ y;
}
F id() { return 0; }  // 0: no tag

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,1}));
    for (int i = 0, op, l, r, k; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r;
            seg.apply(l, r, 1);
        } else {
            cin >> k;
            r = seg.max_right(0, [&](S x){
                return x.sum < k + 1;
            });
            cout << r << '\n';
        }
    }
    return 0;
}

区间加第一个大于x的数

part2 step3C

n个初始为0元素,m次操作。(1<=n,m<=1e5),每次操作

  • 1 l r x : 对所有 l <= i < r 执行 a[i] += x
  • 2 x l : 查询第一个位置 j >= l,满足 a[j] >= x

分析

维护区间和,区间最大值,操作2时在线段树上进行二分。

struct S {
    long long sum, mx;
    int size;
    S():sum(0),mx(0),size(0){} 
    S(long long s, long long m, int siz):sum(s),mx(m),size(siz){}
};
using F = long long;
S op(S x, S y) {
    return S{x.sum + y.sum, max(x.mx, y.mx), x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return S{s.sum + f * s.size, s.mx + f, s.size};
}
F merge(F x, F y) { 
    return x + y;
}
F id() { return 0; }  

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,0,1}));
    for (int i = 0, op, l, r, x; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> x;
            seg.apply(l, r, x);
        } else {
            cin >> x >> l;
            r = seg.max_right(l, [&](S s){
                return s.mx < x;
            });
            cout << (r == n ? -1 : r) << '\n';
        }
    }
    return 0;
}

区间赋值区间加-求和

part2 step4a

n个初始为0元素,m次操作。(1<=n,m<=1e5),每次操作

  • 1 l r x : 对所有 l <= i < r 执行 a[i] = x
  • 2 l r x : 对所有 l <= i < r 执行 a[i] = a[i] + x
  • 3 l r : 求 sum[l…r-1]
struct S {
    long long sum;
    int size;
    S():sum(0),size(0){} 
    S(long long s, int siz):sum(s),size(siz){}
};

struct F{
    int t;  // t=0: 赋值, t=1 加和
    long long v;
};

S op(S x, S y) {
    if (x.size == 0) return y; if (y.size == 0) return x;
    return S{x.sum + y.sum, x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    if (f.t == 1 && f.v == 0) return s;
    return S{(f.t ? s.sum : 0) + s.size * f.v, s.size};
}
F merge(F x, F y) { 
    return x.t == 0 ? x : F{y.t, y.v + x.v};
}
F id() { return F{1, 0}; }  // 

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,1}));
    for (int i = 0, op, l, r, x; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> x;
            seg.apply(l, r, F{0, x});
        } else if (op == 2){
            cin >> l >> r >> x;
            seg.apply(l, r, F{1, x});
        } else {
            cin >> l >> r;
            cout << seg.get(l, r).sum << '\n';
        }
    }
    return 0;
}

区间加等差数列

part2 step4b

n个初始为0元素,m次操作。(1<=n,m<=2e5),每次操作

  • 1 l r x d : 对所有 l <= i < r 执行 a[i] = a[i] + x + (i - l) * d (1 <= l <= r <= n)
  • 2 x : 输出a[x] (1 <= x <= n)

  • 1 <= x, d <= 2e5

分析

在正常区间加的基础上,可以认为第i个位置的size为i,这样在求和时不同位置增加为等差数列,领一个线段树维护每个位置需要减去多少,在处理[l,r]区间时,以下标l为例,第一个线段树维护的时 a[l]=a[l]+l*d 第二个线段树维护 a[l]+=a-l*d 两个线段树相减即为要求的结果。

struct S {
    long long sum;
    int size;
    S():sum(0),size(0){} 
    S(long long s, int siz):sum(s),size(siz){}
};
using F = long long;
S op(S x, S y) {
    return S{x.sum + y.sum, x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return S{s.sum + f * s.size, s.size};
}
F merge(F x, F y) { 
    return x + y;
}
F id() { return 0; }  

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    vector<S> v(n);
    for (int i = 0; i < n; ++i) {
        v[i] = {0, i};
    } 
    LazySegTree<S, op, e, F, tag, merge, id> seg(v);
    LazySegTree<S, op, e, F, tag, merge, id> seg2(vector<S>(n,{0,1}));
    for (int i = 0, op, l, r, a, d; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> a >> d;
            l--;
            seg.apply(l, r, d);
            seg2.apply(l, r, a - d * 1ll * l);
        } else {
            cin >> l;
            l--;
            cout << seg.get(l).sum + seg2.get(l).sum << '\n';
        }
    }
    return 0;
}

区间加求区间乘等差数列和

part2 step4d

给定长度为n个数组a (-100 < a[i] < 100),m次操作。(1<=n,m<=2e5),每次操作

  • 1 l r x: 对所有 l <= i < r 执行 a[i] = a[i] + x (1 <= l <= r <= n, -100 < x < 100)
  • 2 l r : 输出 a[l] * 1 + a[l + 1] * 2 + … + a[r] * (r - l + 1)
struct S {
    long long sum;
    long long size;
    S():sum(0),size(0){} 
    S(long long s, long long siz):sum(s),size(siz){}
};
using F = long long;
S op(S x, S y) {
    return S{x.sum + y.sum, x.size + y.size};
}
S e() {
    return S();
};
S tag(F f, S s) { 
    return S{s.sum + f * s.size, s.size};
}
F merge(F x, F y) { 
    return x + y;
}
F id() { return 0; }  

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    vector<S> a(n), b(n);
    for (int i = 0, x; i < n; ++i) {
        cin >> x;
        a[i] = {(i + 1) * 1ll * x, i + 1};
        b[i] = {x, 1};
    } 
    LazySegTree<S, op, e, F, tag, merge, id> seg(a), seg2(b);
    for (int i = 0, op, l, r, d; i < m; ++i) {
        cin >> op;
        if (op == 1) {
            cin >> l >> r >> d;
            l--;
            seg.apply(l, r, d);
            seg2.apply(l, r, d);
        } else {
            cin >> l >> r;
            l--;
            cout << seg.get(l, r).sum - l * seg2.get(l, r).sum << '\n';
        }
    }
    return 0;
}

区间乘c加d

judge range_affine_point_get

给定长度为n的数组a, q次操作,

  1. 0 l c c d 对所有 l <= i < r 执行 a[i] = a[i] * c + d;
  2. 1 x 输出 a[x] % 998244353
  • 1 <= n, q <= 5e5
  • 0 <= a[i], c, d < 998244353
  • 0 <= l < r < N, 0 <= x < N

方法1:Lazy seg Tree

722ms

struct S {
    mint sum;
    int size;
};
struct F {
    mint c, d;
};
S op(S x, S y) {
    if (x.size == 0) return y;
    if (y.size == 0) return x;
    S s;
    s = S{x.sum + y.sum, x.size + y.size};
    return s;
}
S e() {
    return S{0, 0};
};
S tag(F f, S s) { 
    if (f.c == 1 && f.d == 0) return s;  // 加上这行耗时优化很大
    return S{s.sum * f.c + s.size * f.d, s.size}; 
}
F merge(F x, F y) { return F{x.c * y.c, y.d * x.c + x.d}; }
F id() { return F{1, 0}; }
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    vector<S> a(n, S{0, 1});
    for (int i = 0; i < n; ++i) {
        cin >> a[i].sum;        
    } 
    LazySegTree<S, op, e, F, tag, merge, id> seg(a);
    for (int i = 0, op, l, r, c, d; i < q; ++i) {
        cin >> op;
        if (op == 0) {
            cin >> l >> r >> c >> d;
            seg.apply(l, r, F{c, d});
        } else {
            cin >> l;
            cout << seg.get(l).sum << '\n';
        }
    }

    return 0;
}

对偶线段树

371ms, 时间和空间效率更高

template <typename T, typename F, T(*tag)(F, T), F(*merge)(F, F), F(*id)()>
struct CommutativeDualSegmentTree {
    CommutativeDualSegmentTree() {}
    CommutativeDualSegmentTree(vector<T>&& a) : n(a.size()), m((1 << ceil_lg(a.size()))), data(std::move(a)), lazy(m, id()) {}
    CommutativeDualSegmentTree(const std::vector<T>& a) : CommutativeDualSegmentTree(std::vector<T>(a)) {}
    CommutativeDualSegmentTree(int n, const T& fill_value) : CommutativeDualSegmentTree(std::vector<T>(n, fill_value)) {}

    T operator[](int i) const {
        assert(0 <= i and i < n);
        T res = data[i];
        for (i = (i + m) >> 1; i; i >>= 1) res = tag(lazy[i], res);
        return res;
    }
    T get(int i) const { return (*this)[i];}
    void apply(int l, int r, const F& f) {
        assert(0 <= l and r <= n);
        for (l += m, r += m; l < r; l >>= 1, r >>= 1) {
            if (l & 1) apply(l++, f);
            if (r & 1) apply(--r, f);
        }
    }
protected:
    int n, m;
    vector<T> data;
    vector<F> lazy;

    void apply(int k, const F& f) {
        if (k < m) lazy[k] = merge(f, lazy[k]);
        else data[k - m] = tag(f, data[k - m]);
    }
private:
    static int ceil_lg(int x) {   // minimum non-neg x s.t. `n <= 2^x`
        return x <= 1 ? 0 : 32 - __builtin_clz(x - 1);
    }
};
template <typename T, typename F, T(*tag)(F, T), F(*merge)(F, F), F(*id)()>
struct DualSegmentTree : public CommutativeDualSegmentTree<T, F, tag, merge, id> {
    using base_type = CommutativeDualSegmentTree<T, F, tag, merge, id>;
    using base_type::base_type;
    void apply(int l, int r, const F& f) {
        push(l, r);
        base_type::apply(l, r, f);
    }
private:
    void push(int k) {
        base_type::apply(2 * k, this->lazy[k]), base_type::apply(2 * k + 1, this->lazy[k]);
        this->lazy[k] = id();
    }
    void push(int l, int r) {
        static const int log = __builtin_ctz(this->m);
        l += this->m, r += this->m;
        for (int i = log; (l >> i) << i != l; --i) push(l >> i);
        for (int i = log; (r >> i) << i != r; --i) push(r >> i);
    }
};
mint tag(pair<mint, mint> f, mint x) {
    return f.first * x + f.second;
}
pair<mint, mint> merge(pair<mint, mint> f, pair<mint, mint> g) {
    return { f.first * g.first, f.first * g.second + f.second };
}
pair<mint, mint> id() {
    return { 1, 0 };
}
using Segtree = DualSegmentTree<mint, pair<mint, mint>, tag, merge, id>;

int main() {
    std::ios::sync_with_stdio(false); std::cin.tie(nullptr);

    int n, q;
    std::cin >> n >> q;

    std::vector<mint> a(n);
    for (auto &e : a) {
        cin >> e;
    }
    Segtree seg(a);
    for (int i = 0, op, l, r, c, d; i < q; ++i) {
        cin >> op;
        if (op == 0) {
            cin >> l >> r >> c >> d;
            seg.apply(l, r, {c, d});
        } else {
            cin >> l;
            cout << seg.get(l).val() << '\n';
        }
    } 

    return 0;
}

区间异或查询区间和

cf242 E

一个长度为n的序列,q次操作,每次操作有两种形式

  • 1 l r 输出 a[l,..r]的和
  • 2 l r x 异或操作,对 l <= i <= r 的i,执行 a[i] = a[i] ^ x

  • 1 <= n <= 1e5
  • 1 <= m <= 5e4
  • 0 <= a[i], x <= 1e6
const int K = 20; // 根据值域调整
struct S{
    array<int,K> a;
    int siz;
};
using F = int;
S op(S l, S r) {
    if(l.siz==0)return r;
    if(r.siz==0)return l;
    S s;
    s.siz = l.siz+r.siz;
    for(int i=0;i<K;++i){
        s.a[i]=l.a[i]+r.a[i];
    }
    return s;
}

S e() { return S{}; }

S tag(F l, S r) {
    if(l==0)return r;
    for(int i=0;i<K;++i){
        if((l>>i)&1)r.a[i]=r.siz-r.a[i];
    }
    return r;
}
F merge(F l, F r) { return l^r; }
F id() { return 0; }

void ac_yyf(int tt) {
    int n, q;
    cin >> n;
    using Seg=LazySegTree<S, op, e, F, tag, merge, id>;
    vector<S> a(n); 
    for (int i = 0, x; i < n; ++i) {
        cin >> x;
        for(int j=0;j<K;++j){
            a[i].a[j]=((x>>j)&1);
        }
        a[i].siz=1;
    }
    Seg seg(a);
    cin >> q;
    for (int i = 0, t, l, r, x; i < q; ++i) {
        cin >> t;
        if (t == 1) {
            cin >> l >> r;
            ll ans = 0;
            auto p = seg.get(l - 1, r).a;
            for (int j = 0; j < K; ++j) {
                ans += p[j] * 1ll * (1 << j);
            }
            cout << ans << '\n';

        } else if (t == 2) {
            cin >> l >> r >> x;
            seg.apply(l - 1, r, x);
        } 
    }
}

区间翻转求最长连续1数目

abc 322f

一个长度为n的01序列,q次查询。

  1. 1 l r 将区间[l,r]的1变为0,0变为1
  2. 2 l r 输出区间[l,r]中最长连续1的长度
  • 1 <= q <= 1e5
  • 1 <= n <= 5e5
struct S {
    int pre[2] = {0, 0}; // 前缀0/1长度
    int suf[2] = {0, 0}; // 后缀0/1长度
    int mx[2] = {0, 0};  // 区间连续0/1最大值
    int cnt[2] = {0, 0}; // 区间0/1的数量
};
using F = bool; // 是否取反
S op(S x, S y) {
    S s{};
    for (int i = 0; i < 2; ++i) {
        s.cnt[i] = x.cnt[i] + y.cnt[i];
        s.pre[i] = x.cnt[i ^ 1] ? x.pre[i] : x.cnt[i] + y.pre[i];
        s.suf[i] = y.cnt[i ^ 1] ? y.suf[i] : y.cnt[i] + x.suf[i];
        s.mx[i] = max({x.mx[i], y.mx[i], x.suf[i] + y.pre[i]});
    }
    return s;
}
S e() {
    return S{};
};
S E0() {
    return S{ {1, 0}, {1, 0}, {1, 0}, {1, 0} };
}
S E1() {
    return S{ {0, 1}, {0, 1}, {0, 1}, {0, 1} };
}
S tag(F f, S s) {
    if (!f) return s;
    swap(s.pre[0], s.pre[1]);
    swap(s.cnt[0], s.cnt[1]);
    swap(s.suf[0], s.suf[1]);
    swap(s.mx[0], s.mx[1]);
    return s;
}
F merge(F x, F y) { 
    return x ^ y;
}
F id() { return false; }

void ac_yyf(int tt) {
    int n, q;
    string s;
    cin >> n >> q >> s;
    vector<S> v(n);
    for (int i = 0; i < n; ++i) {
        v[i] = s[i] == '1' ? E1() : E0();
    } 
    LazySegTree<S, op, e, F, tag, merge, id> seg(v);
    for (int i = 0, t, l, r; i < q; ++i) {
        cin >> t >> l >> r;
        if (t == 1) seg.apply(l - 1, r, true);
        else cout << seg.get(l - 1, r).mx[1] << '\n';
    }
}

区间赋值取反查询最大连续1数目

luogu p2572

长度为n的01序列,五种操作

  • 0 l r : [l,r]区间内的所有数全变成 0
  • 1 l r : [l,r]区间内的所有数全变成 1
  • 2 l r : [l,r]区间内的所有数全部取反,0变成1,1变成0
  • 3 l r : [l,r]区间内总共有多少个1
  • 4 l r : [l,r]区间内最多有多少个连续的1

  • 1 <= n, m <= 2e5
struct S {
    int pre[2] = {0, 0}; // 前缀0/1长度
    int suf[2] = {0, 0}; // 后缀0/1长度
    int mx[2] = {0, 0};  // 区间连续0/1最大值
    int cnt[2] = {0, 0}; // 区间0/1的数量
};
using F = array<int, 2>; // (赋值, 取反)
S op(S x, S y) {
    S s{};
    for (int i = 0; i < 2; ++i) {
        s.cnt[i] = x.cnt[i] + y.cnt[i];
        s.pre[i] = x.cnt[i ^ 1] ? x.pre[i] : x.cnt[i] + y.pre[i];
        s.suf[i] = y.cnt[i ^ 1] ? y.suf[i] : y.cnt[i] + x.suf[i];
        s.mx[i] = max({x.mx[i], y.mx[i], x.suf[i] + y.pre[i]});
    }
    return s;
}
S e() {
    return S{};
};
S E0() {
    return S{ {1, 0}, {1, 0}, {1, 0}, {1, 0} };
}
S E1() {
    return S{ {0, 1}, {0, 1}, {0, 1}, {0, 1} };
}
S tag(F f, S s) {
    if (f[0] == -1) {
        if (!f[1]) return s;
        swap(s.pre[0], s.pre[1]);
        swap(s.cnt[0], s.cnt[1]);
        swap(s.suf[0], s.suf[1]);
        swap(s.mx[0], s.mx[1]);
    } else {
        int x = f[0], y = f[0] ^ 1;
        s.pre[x] = s.suf[x] = s.mx[x] = s.cnt[x] = s.cnt[x] + s.cnt[y];;
        s.pre[y] = s.suf[y] = s.mx[y] = s.cnt[y] = 0;
    }
    return s;
}
F merge(F x, F y) { 
    if (x[0] != -1) return F{x[0], 0};
    if (x[1] == 0) {
        return y[0] == -1 ? y : F{y[0], 0};
    } 
    return y[0] == -1 ? F{-1, y[1] ^ 1} : F{y[0] ^ 1, 0};
}
F id() { return {-1, 0}; }

void ac_yyf(int tt) {
    int n, m;
    cin >> n >> m;
    vector<S> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> x;
        a[i] = x == 0 ? E0() : E1();
    }
    LazySegTree<S, op, e, F, tag, merge, id> seg(a);
    for (int i = 0, t, l, r; i < m; ++i) {
        cin >> t >> l >> r;
        r++;
        if (t <= 1) {
            seg.apply(l, r, F{t, 0});
        }
        else if (t == 2) seg.apply(l, r, F{-1, 1});
        else if (t == 3) cout << seg.get(l, r).cnt[1] << '\n';
        else cout << seg.get(l, r).mx[1] << '\n';
    }
}

区间不同元素数目平方和

hackerearch

双周赛116 t4

长度为n的数组a,定义f(l,r)为子数组a[l..r]的不同元素数目,求所有非空子数组f(l,r)的平方和。模1e9+7.

  • 1 <= n <= 2e5
  • 1 <= a[i] <= 2e5

分析

设 last[x] 为上一次出现x的坐标,则

f(l, r) = f(l, r - 1) + 1; // if l > last[nums[r]]
f(l, r) = f(l, r - 1); // if l <= last[nums[r]]

g(i) = f(0,0) + f(0, 1) + ... + f(0, i)

答案即为

ans = g(0)^2 + g(1)^2 + ... + g(n-1)^2

可以按顺序依次维护g(i)^2。

struct S {
    mint s1, s2, size;
};
using F = mint;
S op(S x, S y) {
    return S{x.s1 + y.s1, x.s2 + y.s2, x.size + y.size};
}
S e() {
    return S{};
};
S tag(F f, S s) { 
    if (f == 0) return s;
    S res = s;
    res.s2 = s.s2 + s.size * f;
    res.s1 = s.s1 + 2 * f * s.s2 + s.size * f * f;
    return res;
}
F merge(F x, F y) { return x + y; }
F id() { return 0; }
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    int mx = *max_element(a.begin(), a.end());
    vector<int> p(mx + 1, -1);

    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(n,{0,0,1}));
    mint ans = 0;
    for (int i = 0; i < n; ++i) {
        seg.apply(p[a[i]] + 1, i + 1, 1);
        p[a[i]] = i;
        ans += seg.get(0, i + 1).s1;
    }
    cout << ans << '\n';

    return 0;
}

区间加区间乘积和

atc357f

给定两个长度为n的数组a和b,q次操作:

  • 1 l r x: 对所有 l <= i <= r 执行 a[i] = a[i] + x
  • 2 l r x: 对所有 l <= i <= r 执行 b[i] = b[i] + x
  • 3 l r: 输出 a[l]*b[l] + a[l+1]*b[l+1]+...+a[r]*b[r],模 998244353

  • 1 <= n, q <= 2e5
  • 0 <= a[i], b[i], x <= 1e9
  • 1 <= l <= r <= n
struct S {
    mint sa, sb, sab;
    int len;
};
struct F {
    mint fa, fb;
};
S op(S x, S y) {
    return S{ x.sa + y.sa, x.sb + y.sb, x.sab + y.sab, x.len + y.len };
}
S e() {
    return S{ 0, 0, 0, 0};
};
S tag(F f, S x) { 
    return S{x.sa + f.fa * x.len, x.sb + f.fb * x.len, x.sab + x.sa * f.fb + x.sb * f.fa + f.fa * f.fb * x.len, x.len };
}
F merge(F x, F y) { return F{ x.fa + y.fa, x.fb + y.fb };}
F id() { return F{0,0}; }
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    vector<int> a(n);

    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    vector<S> v(n);
    for (int i = 0, x; i < n; ++i) {
        cin >> x;
        v[i] = S{a[i], x, mint(a[i]) * x, 1};
    } 
    LazySegTree<S, op, e, F, tag, merge, id> seg(v);

    for (int i = 0, t, l, r, x; i < q; ++i) {
        cin >> t;
        if (t == 1) {
            cin >> l >> r >> x;
            seg.apply(l - 1, r, F{ x, 0 });
        } else if (t == 2) {
            cin >> l >> r >> x;
            seg.apply(l - 1, r, F{ 0, x });
        } else {
            cin >> l >> r;
            cout << seg.get(l - 1, r).sab << '\n';
        }
    }
    return 0;
}

权值线段树

权值线段树维护的是大小在[l, r]的范围,一般配合离散化使用。

统计大小在某个范围内的数量

cses1144

一个长度为n的序列,q次操作,每次操作有两种形式

  1. ! k x 将第k个元素修改为x
  2. ? a b 查询元素在[a,b]范围内的数量
  • 1 <= n, q <= 2e5
  • 1 <= a[i], x, a, b <= 1e9
  • 1 <= k <= n

方法1:平衡树(590ms)

#include <bits/stdc++.h>
using namespace std;
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;

template<class T> using ordered_set = tree<T, null_type, less_equal<T>, rb_tree_tag, tree_order_statistics_node_update>;

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n,q;
    cin>>n>>q;
    vector<int> a(n);
    ordered_set<int> s;
    for (int i = 0; i < n; ++i) {
        cin>>a[i];  
        s.insert(a[i]);
    }

    for (int i = 0; i < q; ++i) {
        char c;
        cin >> c;
        if (c == '!') {
            int k, x;
            cin >> k >> x;
            k--;
            s.erase(s.find_by_order(s.order_of_key(a[k])));
            a[k] = x;
            s.insert(x);
        } else {
            int l, r;
            cin >> l >> r;

            cout << s.order_of_key(r + 1) - s.order_of_key(l) << '\n';
        }
    }
    return 0;
}

方法2:树状数组(320ms)

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];    
    }
    vector<array<int, 3>> qs(q);
    char op;
    for (int i = 0; i < q; ++i) {
        cin >> op >> qs[i][1] >> qs[i][2];
        if (op == '!') {
            qs[i][0] = 0;
            a.push_back(qs[i][2]);
        } else {
            qs[i][0] = 1;
            a.push_back(qs[i][1]);
            a.push_back(qs[i][2]);
        }
    }

    Discrete<int> v(a);
    int m = v.size();

    FenwickTree<int> f(m);
    for (int i = 0; i < n; ++i) {
        f.add(v(a[i]), 1);
    }
    for (auto &[op, x, y]: qs) {
        if (op == 0) {
            x--;
            f.add(v(a[x]), -1);
            a[x] = y;
            f.add(v(a[x]), 1);
        } else {
            cout << f.sum(v(x), v(y)) << '\n';
        }
    }

    return 0;
}

方法3:权值线段树(350ms)

using S = int;
S op(S x, S y) {
    S s = x + y;
    return s;
}
S e() {
    return S();
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];    
    }
    vector<array<int, 3>> qs(q);
    char t;
    for (int i = 0; i < q; ++i) {
        cin >> t >> qs[i][1] >> qs[i][2];
        if (t == '!') {
            qs[i][0] = 0;
            a.push_back(qs[i][2]);
        } else {
            qs[i][0] = 1;
            a.push_back(qs[i][1]);
            a.push_back(qs[i][2]);
        }
    }

    Discrete<int> v(a);
    int m = v.size();

    vector<int> p(m);
    for (int i = 0; i < n; ++i) {
        p[v(a[i])]++;
    }

    SegTree<S, op, e> seg(p);

    for (auto &[t, x, y]: qs) {
        if (t == 0) {
            x--;
            seg.set(v(a[x]), seg.get(v(a[x])) - 1);
            a[x] = y;
            seg.set(v(a[x]), seg.get(v(a[x])) + 1);
        } else {
            cout << seg.get(v(x), v(y) + 1) << '\n';
        }
    }
    return 0;
}

查询集合mex

cf 817f

s初始为空集合,执行n次查询:

  • 1 l r 将[l,r]中所有缺失数字插入集合中
  • 2 l r 将[l,r]中所有存在的数从集合中删除
  • 3 l r 将[l,r]中所有存在的数组删除,插入所有不存在的数字

对于每次查询,输出集合的MEX (MEX>=1)

  • 1 <= n <= 1e5
  • 1 <= l <= r <= 1e18

1.权值线段树

将区间离散化,每次查询的MEX值要么是1,要么是某个区间[l,r]的 r+1. 用线段树维护区间中1的数目和0的数目,找到最大的离散化后的下标,满足其0的数目为0.

struct S {
    int x, y;  // x: 1的数量 y: 0的数量
    S():x(0), y(0){}
    S(int x, int y):x(x), y(y){}
};
S op(S x, S y) {
    S s;
    s = S{x.x + y.x, x.y + y.y};
    return s;
}
S e() {
    return S();
};
using F = int;
S tag(F f, S s) { 
    if (f == 0) return s;
    if (f == 1) return S{s.x + s.y, 0};
    if (f == 2) return S{0, s.x + s.y};
    return S{s.y, s.x}; // 01 翻转
}
// 0:删除标记 1:置1 2:置0 3:翻转
F merge(F x, F y) {
    if (x == 0) return y;
    if (x == 1) return 1;
    if (x == 2) return 2;
    if (y == 0) return 3;
    if (y == 3) return 0;
    if (y == 1) return 2;
    if (y == 2) return 1;
}
F id() { return 0; }

template <class T>
struct Discrete {
    vector<T> xs;
    Discrete(const vector<T>& v) {
        xs = v;
        sort(xs.begin(), xs.end());
        xs.erase(unique(xs.begin(), xs.end()), xs.end());
    }
    int get(const T& x) const {
        return lower_bound(xs.begin(), xs.end(), x) - xs.begin();
    }
    inline int operator()(const T& x) const { return get(x); }
    T operator[](int i) { return xs[i]; }
    int size() const { return xs.size(); }
};

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<long long> l(n), r(n), a {1};
    vector<int> t(n);
    for (int i = 0; i < n; ++i) {
        cin >> t[i] >> l[i] >> r[i];
        r[i]++;
        a.push_back(l[i]);
        a.push_back(r[i]);
    }

    Discrete<long long> v(a);
    int m = v.size();
    LazySegTree<S, op, e, F, tag, merge, id> seg(vector<S>(m,{0,1}));
    for (int i = 0; i < n; ++i) {
        seg.apply(v(l[i]), v(r[i]), t[i]);
        int r = seg.max_right(0, [&](S s){
            return s.y == 0;
        });
        cout << v[r] << '\n';
    }
    return 0;
}

打赏一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦