树状数组快速入门题解报告

树状数组快速入门链接:https://www.cnblogs.com/zsxuan/p/17946498

一 基础树状数组,单点修,前缀查 http://oj.daimayuan.top/course/15/problem/634

题意:

\(n\) 个数 \(a_1, a_2, a_3, \cdots, a_n\)
\(q\) 个操作:

  1. 1 x d ,修改 \(a_x = d\)
  2. 2 x ,查询 \(\sum_{i = 1}^{x} a_x\)

\(1 \neq n, q \leq 2 \times 2 \times 10^{5}, 1 \leq a_i, d \leq 10^{9}\), \(1 \leq x \leq n\)

题解:

直接对 \(1 \sim n\) 定义域建出树状数组。朴素的单点修改和前缀查询即可。

单点修改的原理是单点加,需要同时修改 \(a\) 和 树状数组。

view
#include <bits/stdc++.h>

typedef long long ll;
typedef unsigned long long u64;

// ~~ SEG STARST
template<class T>
struct BIT {
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n) { init(n); }

    void init(int n) {
        sz = n; // sz 是树状数组的域
        LGN = 63 - __builtin_clzll(sz); // LGN 是权值的值域
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i)
            s += c[i];
        return s;
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i)
            c[i] += s;
    }

    int find(T s) {
        int pos = 0;
        for (int i = LGN; i >= 0; --i) {
            if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) {
                pos += (1 << i);
                s -= c[pos];
            }
        }
        return pos;
    }
};
// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    std::cin.tie(0); std::ios::sync_with_stdio(false);
	int n, q; std::cin >> n >> q;
    std::vector<ll> a(n + 1);
    BIT<ll> Fe(n);
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        Fe.modify(i, a[i]);
    }
    for (int i = 0; i < q; i++) {
        int typ = 0; std::cin >> typ;
        if (typ == 1) {
            ll x, d; std::cin >> x >> d;
            Fe.modify(x, d - a[x]);
            a[x] = d;
        }
        else {
            int x; std::cin >> x;
            ll ans = Fe.query(x);
            std::cout << ans << "\n";
        }
    }
	return 0;
}

基础树状数组在加法差分下的拓展

基础树状数组如果不通过 \(a\) 维护 \(s\) ,而是通过 \(d\) 维护 \(a\) 。在加法运算下显然可以区间修改,单点查询。
某些情况下具有高效的应用(少推式子)。

二 基于加法差分的树状数组,区间加,区间查

2.1 朴素的加法贡献,区间修改和区间查询 http://oj.daimayuan.top/course/15/problem/635

题意:
给定 \(n\) 个数,一开始都是 \(0\)
支持 \(q\) 个操作:

  1. 1 l r d ,令所有的 \(a_i(l \leq i \leq r)\) 加上 \(d\)
  2. 2 x ,查询 \(\sum_{i = 1}^{x} a_i \bmod 2^{64}\)

\(1 \leq n, q \leq 2 \times 10^{5}, 1 \leq d \leq 10^{9}, 1 \leq l \leq r \leq n\)

题解:

不难考虑用 \(d\) 表示 \(s\) ,有 \(s_x = (x + 1) \sum_{i = 1}^{x} d_i - \sum_{x = 1}^{x} i d_i\)

因为要 \(\bmod 2^{64}\) ,于是权值使用 \(u64\) 类型存储即可。

view
#include <bits/stdc++.h>

typedef long long ll;
typedef unsigned long long u64;

// ~~ SEG STARST
template<class T>
struct BIT_PRO{
    std::vector<T> c1, c2;
    int sz;
    BIT_PRO(){}
    BIT_PRO(int n) { init(n); }

    void init(int n) {
        sz = n;
        c1.resize(sz + 5);
        c2.assign(sz + 5, 0);
        c1.resize(sz + 5);
        c2.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c1[i] += s;
            c2[i] += s * x;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c1[i] * (x + 1);
            s -= c2[i];
        }
        return s;
    }

    void segmodify(int l, int r, T s) {
        modify(l, s);
        modify(r + 1, -s);
    }

    T segquery(int l, int r) {
        return query(r) - query(l - 1);
    }
};

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    std::cin.tie(0); std::ios::sync_with_stdio(false);
	int n, q; std::cin >> n >> q;
    BIT_PRO<u64> Fe(n);
    std::vector<u64> a(n + 2);
    for (int i = 1; i <= n; i++) {
        a[i] = 0;
        Fe.segmodify(i, i, a[i]);
    }
    for (int i = 0; i < q; i++) {
        int typ = 0; std::cin >> typ;
        if (typ == 1) {
            ll l, r, d; std::cin >> l >> r >> d;
            Fe.segmodify(l, r, d);
        }
        else {
            int x; std::cin >> x;
            u64 ans = Fe.query(x);
            std::cout << ans << "\n";
        }
    }
    

	return 0;
}

2.2 区间异或,转 01 串翻转 https://codeforces.com/contest/1955/problem/E

题意:
给定一个长度为 \(n\)\(01\) 字符串 \(s\) 。选择一个 \(k, (1 \leq k \leq n)\) ,可以对 \(s\) 长度为 \(k\) 的任意子段翻转任意次。询问最大的 \(k\) ,使 \(s\) 最终可以成为全 \(1\) 串。

\(1 \leq n \leq 5000\)

题解:

结论: 对于某个 \(k\) ,只需保证 \(s\) 前缀是全 \(1\) 串,对于第一个是 \(0\) 的位置 \(p\ s.t.\ p + k - 1 \leq n\) ,只需翻转 \([p, p + k - 1]\)
证明: 操作只影响路径,不影响结果。\(\square\)

当这个程序执行完毕后,若 \(s\) 是全 \(1\) 串,则 \(k\) 合法。否则不合法。

枚举 \(k \in [1, n]\) \(check\) 出最大 \(k\) 即可。

显然修改是单调的,可以使用类似懒标记的方法做到 \(O(n)\) 修改 。

但是直接暴力用数据结构 \(O(n \log n)\) 区间加也是满足复杂度的。

这里使用数据结构暴力修改的解法。

\(T(n ^ 2 \log n) = 2.5 \times 10^{6} \times 30 = 7.5 \times 10^{8}\) ,时限 \(3\ S\)

使用线段树几乎一定被卡常数,于是可以使用两个树状数组进行区间加。由于是二进制下的加法:异或,需要重新推导式子。

实际上推导过程差不多:

\[\bigoplus_{i = n}^{m} x = \left( ((m + 1) \bmod) x \right ) \oplus \left( n \bmod x \right ) \]

\[\begin{aligned} s_x &= \bigoplus_{i = 1}^{x} a_i \\ &= \bigoplus_{i = 1}^{x} \bigoplus_{j = 1}^{i} d_j \\ &= \bigoplus_{i = 1}^{x} \bigoplus_{j = 1}^{x} [j \leq i] d_j \\ &= \bigoplus_{i = 1}^{x} \bigoplus_{j = 1}^{x} [j \geq i] d_i \\ &= \bigoplus_{i = 1}^{x} \bigoplus_{j = i}^{x} d_i \\ &= ((x + 1) \bmod 2) \bigoplus_{i = 1}^{x} d_i \oplus \bigoplus_{i = 1}^{x} (i \bmod 2) d_i \\ \end{aligned} \]

相较于加法公式推导:加法贡献换成异或贡献的同时,前缀和公式中按照 \(\mod 2\) 意义下计算 \(x + 1\)\(i\) 的乘法贡献。

两个树状数组维护即可。

注意一下异或的逻辑:

  1. 加减法等同异或。
  2. \(\bigoplus_{i = 1}^{n} x = x \oplus (n \bmod 2)\)
view
#include <bits/stdc++.h>

typedef long long ll;

const int N = 5005;
int a[N];

// ~~ SEG STARST
template<class T>
struct BIT_PRO{
    std::vector<T> c1, c2;
    int sz, LGN;
    BIT_PRO(){}
    BIT_PRO(int n) { init(n); }

    void init(int n) {
        sz = n;
        LGN = 63 - __builtin_clzll(n);
        c1.resize(sz + 5);
        c2.assign(sz + 5, 0);
        c1.resize(sz + 5);
        c2.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c1[i] ^= s;
            c2[i] ^= s * (x % 2);
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s ^= c1[i] * ((x + 1) % 2);
            s ^= c2[i];
        }
        return s;
    }

    void segmodify(int l, int r, T s) {
        modify(l, s);
        modify(r + 1, s);
    }

    T segquery(int l, int r) {
        return query(r) ^ query(l - 1);
    }
};

// ~~ SEG END

void solve() {
    int n; std::cin >> n;
    std::string s; std::cin >> s;
    for (int i = 1; i <= n; i++) {
        a[i] = s[i - 1] - '0';
    }
   
    auto check = [&](int k) -> bool {
        BIT_PRO<ll> Fe(n);
        for (int i = 1; i <= n; i++) {
            Fe.segmodify(i, i, a[i]);
        }
        for (int i = 1; i + k - 1 <= n; i++) { // !!! k != 0
            if (Fe.segquery(i, i) == 0) {
                Fe.segmodify(i, i + k - 1, 1);
            }
        }
        int ok = 1;
        for (int i = 1; i <= n; i++) {
            ok &= Fe.segquery(i, i) == 1;
        }
        return ok;
    };

    for (int i = n; i >= 1; --i) {
        if (check(i)) {
            std::cout << i << "\n";
            return;
        }
    }
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    std::cin.tie(0); std::ios::sync_with_stdio(false);
	int _ = 1; std::cin >> _;
    while (_--) { solve(); }
	return 0;
}

2.3 高阶加法差分,无聊的简单推式子 https://www.luogu.com.cn/problem/P1438

题意:

给一个长度为 \(n\) 的数组 \(a_1, a_2, a_3, \cdots, a_n\)

执行 \(m\) 个操作:

  1. 1 l r K D ,对区间 \([l, r]\) 执行 \(a_l += K, a_{l + 1} += K + D, a_{l + 2} += K + 2D, \cdots, a_{r} += K + (r - l)D\)
  2. 2 x ,查询 \(a_x\)

\(1 \leq n, m \leq 10^{5}, 1 \leq a_i, k, d \leq 200, 1 \leq x \leq n\)

题解:

差分数组 \(d\) ,数组 \(a\) ,前缀和数组 \(s\) ,二阶前缀和数组 \(w\)

\(d_{l} += y, d_{r + 1} -= y\) 的影响为:\(s_{l \sim r} += y\)\(w_{i \in [l, r]} += iy, w_{r + 1 \cdots} += (r + l - 1)y\)

于是考虑只需要对 \([l, r]\) 区间加 \(K\)\([l + 1, r]\) 等差加 \(D\)\([r + 1, n]\) 区间减 \((r - (l + 1) - 1) D\)

第一部分。对原数组 \(a\) 建立一组树状数组 \(P\) 以维护它的 \(s\) 数组。执行 \([l, r]\)\(k, d\) 修改时,对 \(P\) 进行 \([l, r]\) 区间均值加。

\(P\) 可以通过两个树状数组 \(P_1, P_2\) 维护,不难推出 \(s_x = (x + 1) \sum_{i = 1}^{x} d_i - \sum_{i = 1}^{x} i \times d_i\)

第二部分。\(0\) 数组建立一组树状数组 \(Q\) 以维护 \(w\) 数组。对 \(Q\) 进行 \([l + 1, r]\) 区间等差加。

思考二阶差分的一些性质:

\(d_x = \Delta(\Delta s_x) = \Delta s_{x} - \Delta s_{x - 1} = s_{x} - 2 s_{x - 1} + s_{x - 2}\)

\[\begin{aligned} \sum_{x = L}^{R} d_x &= \sum_{x = L}^{R} (s_{x} - 2 s_{x - 1} + s_{x - 2}) \\ &= \sum_{x = L}^{R} s_{x} - 2 \sum_{x = L}^{R} s_{x - 1} + \sum_{x = L}^{R} s_{x - 2} \\ &= (s_{R} - s_{L - 1}) - (s_{R - 1} - s_{L - 1}) \end{aligned} \]

\(d_x + k \Rightarrow s_{x} + k, s_{x + 1} + 2k, s_{x + 2} + 3k, \cdots\)

\(w_{l \sim r} + k\)

只需 \(d_{l} + k, d_{r + 1} - k\) ,得到 \(w_{l \sim r} + k, w_{r + 1 \cdots} + (r - l + 1)k\) 。后一部分再用某组树状数组将后缀减去 \((r - l + 1)k\) 即可,不妨用 \(P\)

\(Q\) ,考虑推式子,使得 \(w\)\(d\) 表示:

\[\begin{aligned} w_x &= \sum_{i = 1}^{x} s_i \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{i} a_j \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{i} \sum_{k = 1}^{j} d_k \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{i} \sum_{k = 1}^{i} [k \leq j] d_k \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{i} \sum_{k = 1}^{i} [k \geq j] d_j \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{i} (i - j + 1) d_j \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{x} [j \leq i] (i - j + 1) d_j \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{x} [j \geq i] (j - i + 1) d_i \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{x} [j \geq i] j d_i - \sum_{i = 1}^{x} \sum_{j = 1}^{x} [j \geq i] i d_i + \sum_{i = 1}^{x} \sum_{j = 1}^{x} [j \geq i] d_i \\ &= \sum_{i = 1}^{x} \left (\binom{x + 1}{2} - \binom{i}{2} \right ) d_i - \sum_{i = 1}^{x} (x - i + 1) i d_i + \sum_{i = 1}^{x} (x - i + 1) d_i \\ &= \sum_{i = 1}^{x} d_i \left ( \frac{(x + 1)x}{2} - \frac{i(i - 1)}{2} - (x - i + 1) i + (x - i + 1) \right ) \\ &= \sum_{i = 1}^{x} d_i \left ( \frac{(x + 1)x}{2} - \frac{i(i - 1)}{2} - xi + i^{2} - i + x - i + 1 \right ) \\ &= \left ( \frac{(x + 1)x}{2} + x + 1 \right ) \sum_{i = 1}^{x} d_i - x \sum_{i = 1}^{x} i d_i - \sum_{i = 1}^{x} \left ( \frac{i(i - 1)}{2} - i^{2} + 2i \right ) d_i \\ &= \frac{(x + 1)(x + 2)}{2} \sum_{i = 1}^{x} d_i - x \sum_{i = 1}^{x} i d_i + \sum_{i = 1}^{x} \frac{i^{2} - 3i}{2} d_i \\ \end{aligned} \]

经验算是正确的。

查询 \(P_x + Q_x\)\(1 \sim x\) 的前缀贡献。容斥一下即可。

view
#include <bits/stdc++.h>

typedef long long ll;
typedef unsigned long long u64;

// ~~ SEG STARST
template<class T>
struct BIT_PRO1{
    std::vector<T> c1, c2;
    int sz;
    BIT_PRO1(){}
    BIT_PRO1(int n) { init(n); }

    void init(int n) {
        sz = n;
        c1.resize(sz + 5);
        c1.assign(sz + 5, 0);
        c2.resize(sz + 5);
        c2.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c1[i] += s;
            c2[i] += s * x;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c1[i] * (x + 1);
            s -= c2[i];
        }
        return s;
    }

    void segmodify(int l, int r, T s) {
        modify(l, s);
        modify(r + 1, -s);
    }

    T segquery(int l, int r) {
        return query(r) - query(l - 1);
    }
};

template<class T>
struct BIT_PRO2{
    std::vector<T> c1, c2, c3;
    int sz;
    BIT_PRO2(){}
    BIT_PRO2(int n) { init(n); }

    void init(int n) {
        sz = n;
        c1.resize(sz + 5);
        c1.assign(sz + 5, 0);
        c2.resize(sz + 5);
        c2.assign(sz + 5, 0);
        c3.resize(sz + 5);
        c3.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c1[i] += s;
            c2[i] += s * x;
            c3[i] += s * (x * x - x * 3) / 2;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c1[i] * (x + 1) * (x + 2) / 2;
            s -= c2[i] * x;
            s += c3[i];
        }
        return s;
    }

    void segmodify(int l, int r, T s) {
        modify(l, s);
        modify(r + 1, -s);
    }

    T segquery(int l, int r) {
        return query(r) - query(l - 1);
    }
};
// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    std::cin.tie(0); std::ios::sync_with_stdio(false);
	int n, q; std::cin >> n >> q;
    std::vector<ll> a(n + 2), d(n + 2);
    BIT_PRO1<ll> P(n); BIT_PRO2<ll> Q(n);
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        P.segmodify(i, i, a[i]);
        Q.segmodify(i, i, 0);
    }
    for (int i = 0; i < q; i++) {
        int typ = 0; std::cin >> typ;
        if (typ == 1) {
            ll l, r, K, D; std::cin >> l >> r >> K >> D;
            P.segmodify(l, r, K);
            Q.segmodify(l + 1, r, D);
            P.segmodify(r + 1, n, -D * (r - (l + 1) + 1));
        }
        else {
            int x; std::cin >> x;
            ll ans = P.segquery(x, x) + Q.segquery(x, x);
            std::cout << ans << "\n";
        }
    }    

	return 0;
}

三 Fenwick Tree,倍增

3.1 Fenwick Tree 倍增 http://oj.daimayuan.top/course/15/problem/636

题意:

\(n\) 个数 \(a_1, a_2, a_3, \cdots, a_n\)

支持 \(q\) 个操作:

  1. 1 x d ,修改 \(a_x = d\)
  2. 2 s ,查询最大的 \(T(0 \leq T \leq n)\) 满足 \(\sum_{i = 1}^{T} a_i \leq s\)

\(1 \leq n, q \leq 2 \times 10^{5}, 1 \leq a_i \leq 10^{9}, 1 \leq x \leq n, 1 \leq d \leq 10^{9}, 1 \leq s \leq 10^{14}\)

题解:

比较显然的,\(T\) 是树状数组上通过一个快速到达 \(s\) ,倍增得到。

注意输入 \(s\) 可能大于等于集合的 max ,特判一下倍增结果是否越界。

所以可以直接写代码?

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT {
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n) { init(n); }

    void init(int n) {
        sz = n + 1; // sz 是树状数组的域
        LGN = 63 - __builtin_clzll(sz); // LGN 是权值的值域
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i)
            s += c[i];
        return s;
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i)
            c[i] += s;
    }

    int find(T s) {
        int pos = 0;
        for (int i = LGN; i >= 0; --i) {
            if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) {
                pos += (1 << i);
                s -= c[pos];
            }
        }
        return pos;
    }
};

BIT<ll> C;

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    int n, q; std::cin >> n >> q;
    C.init(n);
    std::vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        C.modify(i, a[i]);
        // std::cout << a[i] << " \n"[i == n];
    }
    for (int i = 0; i < q; i++) {
        int typ = 0; std::cin >> typ;
        if (typ == 1) {
            int x, d; std::cin >> x >> d;
            C.modify(x, d - a[x]);
            a[x] = d;
            // for (int i = 1; i <= n; i++) {
            //     std::cout << C.query(i) - C.query(i - 1) << " \n"[i == n];
            // }
        }
        else {
            ll s; std::cin >> s;
            int T = C.find(s);
            if (T > n) T = n;
            std::cout << T << "\n";
        }
    }
	return 0;
}


3.2 Fenwick Tree 第 K 小 https://www.luogu.com.cn/problem/P1168

观察倍增求 \(max\ Y\ s.t.\ sum_{i = 1}^{T} a_i \leq s\) ,实际上不仅满足 \(\leq s\) ,还包含了一段 \(0\) 的后缀使得 \(\sum_{i = 1}^{T + 1} a_i = s + 1\) 。于是第 \(k\) 小可以是 \(find(s - 1) + 1\)

动态全局第 \(k\)问题可以用堆顶堆解决,也可以直接用 Fenwicik Tree 解决,两者并没有复杂度差距。

动态全局第 \(k\) 小的经典应用是动态中位数。

题意:
给定 \(n\) 个非负整数 \(a_1, a_2, a_3, \cdots, a_n\) ,对前奇数项求中位数。

\(1 \leq n \leq 10^{5}\)\(1 \leq 10^{9}\)

题解:
逐渐加入新的元素求解答案,似乎很像是 \(dp\) ,然而不是。首先 \(dp\) 求解的是最优答案,其次这题无法转移。实际上这是个经典的动态第 \(k\) 小问题。

离散化:
值域很大,但一维点值离散化后值域个数等同于元素个数 \(\times 1\)

  1. 离散化数组 \(numx\) 查出来的 \(rk\) \(0\) 开始
  2. px 往右偏移一位可用 Fenwick Tree 维护。
  3. num[anspx - 1] 即原数组的值。

维护:
考虑 \(1 \sim n\ s.t.\ n - 2 \in odd\) 位求出了中位数,加入后两个数的情况。
可以使用对顶堆
也可直接用 Fenwick Tree ,将点权维护成 \(1\) 的加法贡献,于是可以维护住动态的全局第 \(\frac{n + 1}{2}\) 小。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT {
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n) { init(n); }

    void init(int n) {
        sz = n + 5; // sz 是树状数组的域
        LGN = 63 - __builtin_clzll(sz); // LGN 是权值的值域
        c.resize(sz);
        c.assign(sz, 0);
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i)
            s += c[i];
        return s;
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i)
            c[i] += s;
    }

    int find(T s) {
        int pos = 0;
        for (int i = LGN; i >= 0; --i) {
            if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) {
                pos += (1 << i);
                s -= c[pos];
            }
        }
        return pos;
    }
};

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    int n; std::cin >> n;
    std::vector<int> a(n + 1);
    std::vector<int> numx;
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        numx.push_back(a[i]);
    }
    std::sort(numx.begin(), numx.end());
    numx.erase(std::unique(numx.begin(), numx.end()), numx.end());
    BIT<ll> Fe(n);
    for (int i = 1; i <= n; i++) {
        int px = std::lower_bound(numx.begin(), numx.end(), a[i]) - numx.begin() + 1;
        Fe.modify(px, 1);
        if (i & 1) {
            ll anspx = Fe.find((i + 1) / 2 - 1) + 1;
            std::cout << numx[anspx - 1] << "\n";
        }
    }
	return 0;
}

四 Fenwick Tree ,二维前缀偏序,二维加法偏序

4.1 Fenwick Tree,二维前缀偏序,逆序对 https://www.luogu.com.cn/problem/P1908

题意:

\(n\) 个数 \(a_1, a_2, a_c, \cdots, a_n\) 。输出这个序列的逆序对个数。
\(1 \leq n \leq 5 \times 10^{5}, 1 \leq 10^{9}\)

题解:

逆序对是个经典的可以使用归并排序解决的问题。也是一类二维前缀偏序问题。

但是我们追求优秀、无脑、简单、通用、熟练。所以直接使用 Fenwick Tree

值域很大,离散化成点值个数。注意调整偏移量。

逆序对:顾名思义:\(\forall x, a_y < a_x\ s.t.\ y > x\) 的数量。

显然有二维前缀(后缀)偏序:

\[cnt_{a_j} \in \begin{cases} j > i \\ a_j < a_i \\ \end{cases} \]

从右往左扫,计算 \(cnt\ H(a_j)\ s.t. H(a_j) < H(a_i)\) 的贡献,然后将 \(H(a_i)\) 加入偏序。\(H(x)\)\(x\) 离散化后的数。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT{
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n){ init(n); }

    void init(int n) {
        sz = n + 1;
        LGN = 31 - __builtin_clz(sz);
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c[i] += s;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c[i];
        }
        return s;
    }

    int find(T s) {
        int pos = 0;
        for (int i = LGN; i >= 0; --i) {
            if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) {
                pos += (1 << i);
                s -= c[pos];
            }
        }
        return pos;
    }
};

template<class T>
inline bool read(T &res) {
    char c = getchar(); int sgn;
    if (c == EOF) return 0;
    if (c == '-') sgn = -1, res = 0;
    else sgn = 1, res = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0');
    res *= sgn;
    return 1;
}

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    int n; read<int>(n);
    BIT<ll> Fe(n);
    std::vector<ll> a(n + 1);
    std::vector<ll> numx;
    for (int i = 1; i <= n; i++) {
        read<ll>(a[i]);
        numx.push_back(a[i]);
    }
    std::sort(numx.begin(), numx.end());
    numx.erase(std::unique(numx.begin(), numx.end()), numx.end());
    ll ans = 0;
    for (int i = n; i; --i) {
        int px = std::lower_bound(numx.begin(), numx.end(), a[i]) - numx.begin() + 1;
        ans += Fe.query(px - 1);
        Fe.modify(px, 1);
    }
    std::cout << ans << "\n";
	return 0;
}

4.2 Fenwick Tree,二维前缀偏序,LIS https://acm.hdu.edu.cn/showproblem.php?pid=5256

题意:

\(n\) 个数的序列为 \(a_1, a_2, a_3, \cdots, a_n\) 。询问最少修改多少个数可使得序列严格递增。

\(1 \leq n \leq 10^{5}, 1 \leq a_i \leq 10^{6}\)

题解:

不难想到,在给定偏序下,每个数在偏序上的相对位置固定。

不难考虑,我们找到一个符合偏序的极大子序列,即最长上升子序列。将其他数全部并入这个序列便能实现整个序列的偏序。

如果给定的是非全偏序 \((G, \leq)\) ,上述这就是答案。

根据整数的离散性 \(|x - y| \geq 1\) ,如果相邻两个数是 \(1\) ,我们没法在中间插入一个数。

如果给定的是全偏序 \((G, <)\) 某个数不一定可以并入选择的极大序列。

这是一个经典结论题。

这里我们构造一个新序列和原序列的解集双射。

核心在于 \(\forall x \in [1, n], a_x = a_x - x\)

容易证明执行这个操作后,每个新的非降子序列和每个旧的上升子序列双射。
证明:
充分性:若一个序列是非降子序列,则 \(a_x = a_x + x\) 后,得到的序列是上升子序列。
必要性:若一个序列是上升子序列,则 \(a_x = a_x - x\) 后,得到的序列是非降子序列。

\(\square\)

求解最长非降子序列长度 \(len\)\(n - len\) 即最少要修改的个数。

考虑求解最长上升子序列 \(LIS\) ,典型的有 DP \(dp_i = dp_j + 1\ s.t. j < i, a_j < a_i, max(dp_{j})\)
显然有二维前缀偏序:

\[max_{a_j} \in \begin{cases} j < i \\ a_j < a_i \\ \end{cases} \]

最长非降子序列只需把限制 \(a_j < a_i\) 换成 \(a_j \leq a_i\)

具体的实现,需要让 \(a_x = a_x + n + 1\) ,再 \(a_x = a_x - x\) ,避开了非正整数。然后再离散化 \(a\) 数组。

离散化过的数是不能改的!!!注意先修改再离散化。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT{
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n){ init(n); }

    void init(int n) {
        sz = n + 1;
        LGN = 31 - __builtin_clz(sz);
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c[i] = std::max(c[i], s);
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s = std::max(s, c[i]);
        }
        return s;
    }
};

template<class T>
inline bool read(T &res) {
    char c = getchar(); int sgn;
    if (c == EOF) return 0;
    if (c == '-') sgn = -1, res = 0;
    else sgn = 1, res = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0');
    res *= sgn;
    return 1;
}

template<class T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif
    int testcase; read<int>(testcase);
    for (int test = 1; test <= testcase; test++) {
        printf("Case #%d:\n", test);
        int n; read<int>(n);
        BIT<ll> Fe(n + 1);
        std::vector<ll> a(n + 1);
        std::vector<ll> numy;
        for (int i = 1; i <= n; i++) {
            read<ll>(a[i]);
            a[i] += n + 1 - i;
            numy.push_back(a[i]);
        }
        std::sort(numy.begin(), numy.end());
        numy.erase(std::unique(numy.begin(), numy.end()), numy.end());
        std::vector<ll> dp(n + 1);
        dp[0] = 0;
        ll ans = 0;
        for (int i = 1; i <= n; i++) {
            int py = std::lower_bound(numy.begin(), numy.end(), a[i]) - numy.begin() + 1;
            dp[i] = Fe.query(py) + 1;
            ans = std::max(ans, dp[i]);
            Fe.modify(py, dp[i]);
        }
        write<ll>(n - ans); puts("");
    }
	return 0;
}

4.3 Fenwick Tree,二维加法偏序,最大和上升子序列 http://oj.daimayuan.top/course/15/problem/780

题意:

给一个长度为 \(n\) 的数组 \(a_1, a_2, a_3, \cdots, a_n\) ,问其中的和最大的上升子序列。
也就是说,找到 \(1 \leq p_1 < p_2 < p_3 < \cdots < p_m \leq n\)\(a_{p_1} < a_{p_2} < a_{p_3} < \cdots < a_{p_m}\) ,并使得 \(\sum_{i = 1}^{m} a_{p_i}\) 最大。

\(1 \leq n \leq 2 \times 10^{5}, 1 \leq a_i \leq 2 \times 10^{5}\)

题解:

最优结果,考虑暴力 DP 怎么做。设以 \(i\) 为结尾,最大和上升子序列结果为 \(f_{i}\) ,则有 \(f_{i} = f_{j} + a_{i}\ s.t.\ j < i, a_j < a_i, max(f_{j})\)

显然的二维前缀偏序:

\[max_{f_j} \in \begin{cases} j < i \\ a_j < a_i \end{cases} \]

于是用 Fenwick Tree 维护 DP 。

如果 \(a\) 的值域很大也能做,\(a_x\) 从离散数组 \(numy\) 中离散化出的结果为 \(py\) ,即有 \(a_i = numy[py - 1]\)

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT{
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n){ init(n); }

    void init(int n) {
        sz = n + 1;
        LGN = 31 - __builtin_clz(sz);
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c[i] = std::max(c[i], s);
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s = std::max(s, c[i]);
        }
        return s;
    }
};

template<class T>
inline bool read(T &res) {
    char c = getchar(); int sgn;
    if (c == EOF) return 0;
    if (c == '-') sgn = -1, res = 0;
    else sgn = 1, res = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0');
    res *= sgn;
    return 1;
}

template<class T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif
    int n; read<int>(n);
    std::vector<ll> a(n + 1);
    ll K = 0;
    for (int i = 1; i <= n; i++) {
        read<ll>(a[i]);
        K = std::max(K, a[i]);
    }
    BIT<ll> Fe(K);
    std::vector<ll> dp(n + 1);
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        dp[i] = Fe.query(a[i] - 1) + a[i];
        ans = std::max(dp[i], ans);
        Fe.modify(a[i], dp[i]);
    }
    write<ll>(ans); puts("");

	return 0;
}

五 狭义扫描线

需要二维偏序都能算是扫描线,但扫描线不是二维偏序。

狭义扫描线是:需要离线二维数点问题的解决方案

二维加法偏序一定是二维数点,二维数点不一定是二维偏序。(如矩形面积并不是二维偏序,但是二维数点)

5.1 Fenwick Tree,二维加法偏序,离散化,扫描线,容斥 http://oj.daimayuan.top/course/15/problem/686

题解:

  1. 将操作和询问离线并排序。
  2. 扫描线直接扫描二维问题时,通常顺序扫描 \(y\) 轴方便。于是点的竖直优先级最高。
  3. 次优询问和修改的顺序。点的“水平优先级、所属询问的 \(id\)” 顺序不重要。

修改直接插入一个点,不妨编号成第 \(0\) 个询问。

询问矩形 \((x_1, y_1), (x_2, y_2)\) ,则插入 \((x_2, y_2), (x_1 - 1, x_2 - 1), (x_2, y_1 - 1), (x_1 - 1, y_2)\) 用来容斥矩形,编号第 \(i\) 个询问。
询问优先级:

  1. 四个点排序优先级是前两个减去后两个。
  2. 询问优先级低于修改。

扫描线只需要离散化另一维:由于事件被排序成了从 \(y\) 轴向上扫描。只需要离散化 \(x\) 轴的需要修改的坐标。

遇到修改事件时,进行修改。优先级高于询问事件。

遇到询问事件时,调整事件对应询问的答案。

询问时的二维偏序是:

\[cnt_{x^{'}, y^{'}} \in \begin{cases} y_{i_1} \leq y^{'} \leq y_{i_{2}} \\ x_{i_1} \leq x^{'} \leq x_{i_{2}} \\ \end{cases} \]

修改时往 Fenwick Tree 插入一个点。

一个开闭区间离散化的技术处理:
正常情况下在离散数组中直接 lower_bound[x] 就能找到 x 离散出的值。

但是容斥矩形的四个点存在开闭区间的处理,只看 \(x\) ,有 \(x\) 或者 \(x - 1\) 。但 \(x - 1\) 并不在离散数组中。

\(\leq (x - 1)_{open}\) 的询问,实则等同于开区间 \(x - 1\) 左侧一个点 \(\leq (x^{'})_{closed}\) 的询问。

这时候 uper_bound[x] - 1 才能保证找到的开闭区间的端点都合法。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT{
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n){ init(n); }

    void init(int n) {
        sz = n + 1;
        LGN = 31 - __builtin_clz(sz);
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c[i] += s;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c[i];
        }
        return s;
    }
};

template<class T>
inline bool read(T &res) {
    char c = getchar(); int sgn;
    if (c == EOF) return 0;
    if (c == '-') sgn = -1, res = 0;
    else sgn = 1, res = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0');
    res *= sgn;
    return 1;
}

template<class T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif
    int n, q; read<int>(n); read<int>(q);
    std::vector<std::array<int, 4> > event; // y, priority, x, id
    std::vector<int> vx;
    for (int i = 1; i <= n; i++) {
        int x, y; read<int>(x); read<int>(y);
        vx.push_back(x);
        event.push_back({y, 0, x, 0});
    }
    for (int i = 1; i <= q; i++) {
        int x1, x2; read<int>(x1); read<int>(x2);
        int y1, y2; read<int>(y1); read<int>(y2);
        event.push_back({y2, 1, x2, i});
        event.push_back({y1 - 1, 1, x1 - 1, i});
        event.push_back({y2, 2, x1 - 1, i});
        event.push_back({y1 - 1, 2, x2, i});
    }
    std::sort(event.begin(), event.end());
    std::sort(vx.begin(), vx.end());
    vx.erase(std::unique(vx.begin(), vx.end()), vx.end());
    BIT<int> Fe((int)vx.size());
    std::vector<ll> ans(q + 1);
    for (auto evt : event) {
        if (evt[1] == 0) {
            int px = std::lower_bound(vx.begin(), vx.end(), evt[2]) - vx.begin() + 1;
            Fe.modify(px, 1);
        }
        else {
            int px = std::upper_bound(vx.begin(), vx.end(), evt[2]) - vx.begin();
            int tmp = Fe.query(px);
            if (evt[1] == 1) ans[evt[3]] += tmp;
            else ans[evt[3]] -= tmp;
        }
    }
    for (int i = 1; i <= q; i++) {
        write<ll>(ans[i]); puts("");
    }
	return 0;
}

5.2 Fenwick Tree,二维前缀偏序/二维加法偏序,询问离线,扫描线?区间不同数之和 http://oj.daimayuan.top/course/15/problem/687

题意:
\(n\) 个数 \(a_1, a_2, a_3, \cdots, a_n\)
\(q\) 个询问,每次询问区间 \([l, r]\) 里不同数字之和(即一个数字的权值只算一次加法贡献)。
\(1 \leq n,q \leq 2 \times 10^{5}, 1 \leq a_i \leq n, 1 \leq l \leq r \leq n\)

题解:
\(a_i \leq n\) 没啥意义,和偏序无关。

需要一个技术处理:
技术处理: \(for\ i = 1\ to\ n\) ,在线维护 \(pre_i\) 为上一个 \(a_i\) 出现的位置。
具体需要处理一个 last[a[i]] = i, pre[i] = last[a[i]] 。

存在两种方法:

方法 1:
狭义扫描线

需要离线的非全序的二维加法偏序。都等价于离线的二维数点,都能用狭义扫描线处理。

全序显然比非全序更好解决,直接广义扫描线就能处理。

观察任意区间 \([l, r]\),若 \(i \in [l, r]\) ,则 \(pre_i < l\) 代表 \(a_i\)\([l, r]\) 中是第一次出现。

这是个经典的处理,类似的有:https://www.luogu.com.cn/problem/P8773(选数异或)

即是一个二维偏序:

\[\sum a_x \in \begin{cases} L \leq x \leq R \\ pre_x < L \\ \end{cases} \]

这个区间只要用两个矩形就能容斥。

\(pre_i\)\(y\) 轴,\(i\)\(x\) 轴,扫描线沿 \(y\) 轴向上扫。修改和询问 \(x\) 轴上的点。

event 记录 x,y,操作优先级,操作 id 。确保 y 为第一优先级(扫描轴),操作优先级为第二优先级。其他优先级不重要。

  1. 修改:for i = 1 to n,\(\{pre_i, 0, i, 0\}\)
  2. 询问:for i = 1 to q,\(\{l - 1, 1, r, i\},\ \{l - 1, 2, l, i\}\)
view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT{
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n){ init(n); }

    void init(int n) {
        sz = n + 1;
        LGN = 31 - __builtin_clz(sz);
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c[i] += s;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c[i];
        }
        return s;
    }
};

template<class T>
inline bool read(T &res) {
    char c = getchar(); int sgn;
    if (c == EOF) return 0;
    if (c == '-') sgn = -1, res = 0;
    else sgn = 1, res = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0');
    res *= sgn;
    return 1;
}

template<class T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

// ~~ SEG END
const int N = 2E5 + 10;
int last[N];

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif
    int n, q; read<int>(n); read<int>(q);
    BIT<ll> Fe(n + 1);
    std::vector<int> a(n + 1);
    std::vector<std::array<int, 4> > event; // y, priority, x, id
    std::vector<int> pre(n + 1);
    for (int i = 1; i <= n; i++) {
        read<int>(a[i]);
        pre[i] = last[a[i]];
        event.push_back({pre[i], 0, i, 0});
        last[a[i]] = i;
    }
    for (int i = 1; i <= q; i++) {
        int l, r; read<int>(l); read<int>(r);
        event.push_back({l - 1, 1, r, i});
        event.push_back({l - 1, 2, l - 1, i});
    }
    std::sort(event.begin(), event.end());
    std::vector<ll> ans(q + 1);
    for (auto evt : event) {
        if (evt[1] == 0) { // modify 不涉及 0
            Fe.modify(evt[2], a[evt[2]]);
        }
        else {
            if (evt[1] == 1) ans[evt[3]] += Fe.query(evt[2]);
            else ans[evt[3]] -= Fe.query(evt[2]);
        }
    }
    for (int i = 1; i <= q; i++) {
        write<ll>(ans[i]); puts("");
    }
    for (int i = 1; i <= n; i++) {
        last[a[i]] = 0;
    }
	return 0;
}

方法 2:
维护每个点到当前的更新点的答案。

for r = 1 to n 作为更新点。

观察任意区间 \([l, r]\) 。对每个 \(l\) 维护 \(ans_l\) 表示,\(l\)\(r\) 的答案。

考虑更新:\(r - 1 \rightarrow r\) 时,所有 \(ans_x\) 受到的影响。

观察到任意某个区间 \([l, r]\) ,若 \(a_r\) 没出现过,则 \(ans\) 受到的影响为: \(x \in [l, r],\ ans_x += a_r\)
显然 \(a_r\) 没出现过的区间是 \([pre_r + 1, r]\) ,有 \(ans_x += a_r\ s.t.\ pre_r + 1 \leq x \leq r\)

利用树状数组区间修改,单点查询。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT{
    std::vector<T> c;
    int sz, LGN;
    BIT(){}
    BIT(int n){ init(n); }

    void init(int n) {
        sz = n + 1;
        LGN = 31 - __builtin_clz(sz);
        c.resize(sz + 5);
        c.assign(sz + 5, 0);
    }

    void modify(int x, T s) {
        assert(x > 0);
        for (int i = x; i <= sz; i += i & -i) {
            c[i] += s;
        }
    }

    T query(int x) {
        assert(x <= sz);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            s += c[i];
        }
        return s;
    }
};

template<class T>
inline bool read(T &res) {
    char c = getchar(); int sgn;
    if (c == EOF) return 0;
    if (c == '-') sgn = -1, res = 0;
    else sgn = 1, res = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0');
    res *= sgn;
    return 1;
}

template<class T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

// ~~ SEG END
const int N = 2E5 + 10;
int last[N];
signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif
    int n, q; read<int>(n); read<int>(q);
    BIT<ll> Fe(n + 1);
    std::vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        read<int>(a[i]);
    }
    std::vector<int> l(q + 1), r(q + 1);
    std::vector<std::vector<std::array<int, 2> > > belong(n + 1);
    for (int i = 1; i <= q; i++) {
        int l, r; read<int>(l); read<int>(r);
        belong[r].push_back({l, i});
    }
    std::vector<ll> ans(q + 1);
    int pre = 0;
    for (int r = 1; r <= n; r++) {
        pre = last[a[r]];
        Fe.modify(pre + 1, a[r]);
        Fe.modify(r + 1, -a[r]);
        last[a[r]] = r;
        for (auto blg : belong[r]) {
            ans[blg[1]] = Fe.query(blg[0]);
        }
    }
    for (int i = 1; i <= q; i++) {
        write<ll>(ans[i]); puts("");
    }
    for (int i = 1; i <= n; i++) {
        last[a[i]] = 0;
    }
	return 0;
}

六 高维树状数组

每一维单次操作的时间复杂度 \(O(\log n)\) ,所以高维树状数组的单次操作的复杂度 \(O((\log n)^{x})\)

6.1 二维单点修改,区间查询 http://oj.daimayuan.top/course/15/problem/637

题意:

\(n \times m\) 个数 \(a_{1, 1}, a_{1, 2}, a_{1, 3}, \cdots, a_{1, m}, \cdots, a_{n, m}\)
支持 \(q\) 个操作:

  1. 1 x y d ,修改 \(a_x, y = d\)
  2. 2 x y ,查询 \(\sum_{i = 1}^{x} \sum_{j = 1}^{y} a_{i, j}\)

\(1 \leq n, m \leq 500, 1 \leq q \leq 2 \times 10^{5}, a_{i, j} 1 \leq a_{i, j} \leq 10^{9}\)
\(1 \leq x \leq n, 1 \leq y \leq m, 1 \leq d \leq 10^{9}\)

题解:

就是很朴素的二维区间上维护树状数组,每一维都基于 lowbit 维护。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT_2D{
    std::vector<std::vector<T> > c;
    int szx, szy;
    BIT_2D(){}
    BIT_2D(int n, int m){ init(n, m); }

    void init(int n, int m) {
        szx = n + 1;
        szy = m + 1;
        c.resize(szx + 5);
        for (auto &u : c) u.resize(szy + 5);
        for (auto &u : c) u.assign(szy + 5, 0);
    }

    void modify(int x, int y, T s) {
        assert(x > 0 && y > 0);
        for (int i = x; i <= szx; i += i & -i) {
            for (int j = y; j <= szy; j += j & -j) {
                c[i][j] += s;
            }
        }
    }

    T query(int x, int y) {
        assert(x <= szx && y <= szy);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            for (int j = y; j; j -= j & -j) {
                s += c[i][j];
            }
        }
        return s;
    }
};

// ~~ SEG END

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    int n, m, q; std::cin >> n >> m >> q;
    BIT_2D<ll> Fe(n, m);
    std::vector<std::vector<ll> > g(n + 1, std::vector<ll>(m + 1));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            std::cin >> g[i][j];
            Fe.modify(i, j, g[i][j]);
        }
    }
    for (int i = 0; i < q; i++) {
        int typ = 0; std::cin >> typ;
        if (typ == 1) {
            ll x, y, d; std::cin >> x >> y >> d;
            assert(x <= n && y <= m);
            Fe.modify(x, y, d - g[x][y]);
            g[x][y] = d;
        }
        else {
            int x, y; std::cin >> x >> y;
            ll ans = Fe.query(x, y);
            std::cout << ans << "\n";
        }
    }
	return 0;
}

6.2 二维区间加,区间查询 https://loj.ac/p/135

题意:

给一个大小为 \(n \times m\)\(0\) 矩阵。直到文件输入结束,执行若干个操作。
操作有两类:

  1. 1 a b c d x ,表示将右上角 \((a, b)\) 到左下角 \((c, d)\) 的子矩阵加上 \(x\)
  2. 2 a b c d ,表示询问右上角 \((a, b)\) 到左下角 \((c, d)\) 的子矩阵数字和。

令其是笛卡尔坐标系。给的是零矩阵,不需要关注端点。

否则笛卡尔坐标系的前缀是左下角,不妨 \(swap(a, c)\)

题解:

微笑着推式子吧:

\[\begin{aligned} s_{x, y} &= \sum_{i = 1}^{x} \sum_{j = 1}^{y} a_{i, j} \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{y} \sum_{k = 1}^{i} \sum_{l = 1}^{j} d_{k, l} \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{y} \sum_{k = 1}^{x} \sum_{l = 1}^{y} [k \leq i] [l \leq j] d_{k, l} \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{y} \sum_{k = 1}^{x} \sum_{l = 1}^{y} [k \geq i] [l \geq j] d_{i, j} \\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{y} (x - i + 1) (y - j + 1) d_{i, j} \\ \textbf{分离常量}\\ &= \sum_{i = 1}^{x} \sum_{j = 1}^{y} \left ( (x + 1)(y + 1) - j(x + 1) - i(y + 1) + ij \right ) d_{i, j} \\ &= (x + 1) (y + 1)\sum_{i = 1}^{x} \sum_{j = 1}^{y} d_{i, j} - (x + 1) \sum_{i = 1}^{x} \sum_{j = 1}^{y} j d_{i, j} - (y + 1) \sum_{i = 1}^{x} \sum_{j = 1}^{y} i d_{i, j} + \sum_{i = 1}^{x} ij d_{i, j} \\ \end{aligned} \]

建立四个树状数组 \(P, Q, H, W\)
询问时分别补上 \(\times (x + 1)(y + 1), \times x, \times y, \times 1\) 的贡献。这是显然的。
修改时分别补上 \(\times 1, \times y, \times x, \times xy\) 的贡献。考虑原数组树状数组的贡献。

view
#include <bits/stdc++.h>

typedef long long ll;

// ~~ SEG STARST
template<class T>
struct BIT_2D_PRO {
    std::vector<std::vector<T> > c1, c2, c3, c4;
    int szx, szy;

    BIT_2D_PRO(){}
    BIT_2D_PRO(int n, int m) { init(n, m); }

    void init(int n, int m) {
        szx = n; szy = m;
        c1.resize(szx + 5);
        for (auto &u : c1) u.resize(szy + 5);
        for (auto &u : c1) u.assign(szy + 5, 0);
        c2.resize(szx + 5);
        for (auto &u : c2) u.resize(szy + 5);
        for (auto &u : c2) u.assign(szy + 5, 0);
        c3.resize(szx + 5);
        for (auto &u : c3) u.resize(szy + 5);
        for (auto &u : c3) u.assign(szy + 5, 0);
        c4.resize(szx + 5);
        for (auto &u : c4) u.resize(szy + 5);
        for (auto &u : c4) u.assign(szy + 5, 0);
    }

    void modify(int x, int y, T s) {
        assert(x > 0 && y > 0);
        for (int i = x; i <= szx; i += i & -i) {
            for (int j = y; j <= szy; j += j & -j) {
                c1[i][j] += s;
                c2[i][j] += s * y;
                c3[i][j] += s * x;
                c4[i][j] += s * x * y;
            }
        }
    }

    T query(int x, int y) {
        assert(x <= szx && y <= szy);
        T s = 0;
        for (int i = x; i; i -= i & -i) {
            for (int j = y; j; j -= j & -j) {
                s += c1[i][j] * (x + 1) * (y + 1);
                s -= c2[i][j] * (x + 1);
                s -= c3[i][j] * (y + 1);
                s += c4[i][j];
            }
        }
        return s;
    }

    void recmodify(int a, int b, int c, int d, ll s) {
        modify(a, b, s);
        modify(a, d + 1, -s);
        modify(c + 1, b, -s);
        modify(c + 1, d + 1, s);
    }

    T recquery(int a, int b, int c, int d) {
        return query(c, d) - query(a - 1, d) - query(c, b - 1) + query(a - 1, b - 1);
    }
};
// ~~ SEG END

// 注意启用后关闭 cin cout 缓冲 优化且对 c++ oi流 停用
template<class T>
inline bool read(T &ret) {
    char c; int sgn;
    if (c = getchar(), c == EOF) return 0; // 先判结尾
    while (c != '-' && (c < '0' || c > '9')) c = getchar(); //再判是一个数字
    sgn = (c == '-') ? -1 : 1;
    ret = (c == '-') ? 0 : (c - '0');
    while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0'); //直到读完
    ret *= sgn;
    return 1; 
}

template<class T>
inline void write(T x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

signed main() {
#ifndef ONLINE_JUDGE
freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout);
#endif 
    int n, m; read<int>(n); read<int>(m);
    BIT_2D_PRO<ll> Fe(n, m);
    std::vector<std::vector<ll> > g(n + 2, std::vector<ll>(m + 2));
    std::vector<std::vector<ll> > d(n + 2, std::vector<ll>(m + 2));
    // for (int i = 1; i <= n; i++) {
    //     for (int j = 1; j <= m; j++) {
    //         g[i][j] = 0;
    //         d[i][j] = g[i][j] - g[i - 1][j] - g[i][j - 1] + g[i - 1][j - 1];
    //         Fe.recmodify(i, j, i, j, d[i][j]);
    //     }
    // }
    int typ;
    while (read<int>(typ)) {
        if (typ == 1) {
            int a, b, c, d, x;
            read<int>(a); read<int>(b); read<int>(c); read<int>(d); read<int>(x);
            Fe.recmodify(a, b, c, d, x);
        }
        else {
            int a, b, c, d;
            read<int>(a); read<int>(b); read<int>(c); read<int>(d);
            ll ans = Fe.recquery(a, b, c, d);
            write<ll>(ans); puts("");
        }
    }

	return 0;
}
posted @ 2024-04-10 21:29  zsxuan  阅读(19)  评论(0编辑  收藏  举报