扫描线历史和问题

4-side 矩形加 & 求和

给定一个 \(n \times n\) 的二维平面,先进行 \(n\) 次 4-side 矩形加,再查询 \(n\) 次 4-side 矩形内部的整点和。

\(n \le 5 \times 10^5\)

我们将修改矩形和查询矩形都差分成如图所示的 3-side 矩形:

\(x\) 轴扫描线,并在 \(y\) 轴上维护序列数据结构。考察这个数据结构要维护的事情:

  1. 在扫描到修改矩形的左边界时,我们要做一个区间加:

  2. 在扫描到查询矩形的右边界时,我们要做一个区间求和:

\(x\) 轴视作时间轴。感性理解一下,我们希望每个区间求和随时间推移不断产生贡献。将问题抽象为数据结构语言:

维护两个长为 \(n\) 的序列 \(a_i,b_i\),支持如下操作:

  • 给定 \(l,r,v\),对 \(i \in [l, r]\)\(a_i \gets a_{i}+v\)
  • \(i \in [1,n]\)\(b_i \gets b_i +a_i\)
  • 给定 \(l,r\),求 \(\sum \limits_{i=l}^r b_i\)

上述定义中,\(a_i\)\(y\) 轴信息的原序列,\(b_i\)\(y\) 轴信息的历史和

对于历史和问题,通用的做法是将信息设计为向量,标记设计为矩阵,从而信息合并是向量加法,标记合并是矩阵乘法。对于本题,我们考虑将信息设计为 $$\begin{bmatrix} \sum a_i \ \sum b_i \ \text{len} \end{bmatrix}$$,其中 \(\text{len}\) 表示区间长度。对操作进行讨论:

  • 区间加 \(v\)\(\begin{bmatrix} 1 & 0 & v \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\begin{bmatrix} \sum a_i \\ \sum b_i \\ \text{len} \end{bmatrix}=\begin{bmatrix} \sum (a_i+v) \\ \sum b_i \\ \text{len} \end{bmatrix}\)
  • 更新历史和:\(\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\begin{bmatrix} \sum a_i \\ \sum b_i \\ \text{len} \end{bmatrix}=\begin{bmatrix} \sum a_i \\ \sum (a_i+b_i) \\ \text{len} \end{bmatrix}\)

直接使用矩阵会获得巨大常数。事实上,可以只维护形如 \(\begin{bmatrix} 1 & 0 & x \\ y & 1 & z \\ 0 & 0 & 1 \end{bmatrix}\) 的标记,手动模拟矩阵乘法,称这一优化为半群优化

复杂度 \(O(n \log n)\)

BZOJ4262 Sum

给定一个长为 \(n\) 的序列 \(a_i\)\(q\) 次询问给定 \(l_1,r_1,l_2,r_2\),查询

\[\sum_{l \in [l_1,r_1]} \sum_{r \in [l_2,r_2]} \left(\max_{i \in [l,r]} a_i - \min_{i \in [l,r]} a_i\right) \]

\(n=10^5\)\(q \le 4 \times 10^4\),1 秒,512 MB。

显然一次询问的结果可以拆成 \(\max\)\(\min\) 相减。下面以 \(\max\) 的处理为例,\(\min\) 的处理同理。

考虑每个数 \(a_i\) 的贡献。用单调栈预处理出 \(a_i\) 为最大值的极长区间 \([x_i,y_i]\),则 \(a_i\)\(l \in [x_i,i], r\in [i, y_i]\)\((l,r)\) 都能产生一次贡献。将 \(l,r\) 刻画到二维平面上,此时每个数 \(a_i\) 相当于一个 4-side 矩形 \([x_i,i] \times [i,y_i]\),询问则是查询 \([l_1,r_1] \times [l_2,r_2]\) 这个 4-side 矩形内部整点的点权和。

这里有一个细节:\(x_i\) 的定义是左侧第一个 \(\textcolor{red}{\ge} a_i\) 的下标 \(+1\)\(y_i\) 的定义则是右侧第一个 \(\textcolor{red}{>} a_i\) 的下标 \(-1\)。也就是,我们将数视作 \((a_i,i)\) 的有序二元组,防止一个数被算重。

现在问题被转化成先做 4-side 矩形加,再做 4-side 矩形求和的问题,扫描线历史和解决。视 \(n,q\) 同阶复杂度 \(O(n \log n)\)

:::::info[代码]

const int N = 1e5 + 5;
int n, m, a[N], x[N], y[N]; i64 ans[N];
vector<tuple<int, int, i64>> U[N];  // (y1, y2, v)
vector<tuple<int, int, int, int>> Q[N];   // (y1, y2, type, id)
void add(int x1, int y1, int x2, int y2, i64 v) {
    U[x1].emplace_back(y1, y2, v);
    U[x2+1].emplace_back(y1, y2, -v);
}
void prework() {
    stack<int> st;
    for (int i = 1; i <= n; i++) {
        while (!st.empty() && a[st.top()] <= a[i]) st.pop();
        x[i] = st.empty() ? 1 : st.top()+1, st.emplace(i);
    }
    while (!st.empty()) st.pop();
    for (int i = n; i >= 1; i--) {
        while (!st.empty() && a[st.top()] < a[i]) st.pop();
        y[i] = st.empty() ? n : st.top()-1, st.emplace(i);
    }
    for (int i = 1; i <= n; i++) add(x[i], i, i, y[i], a[i]);

    while (!st.empty()) st.pop();
    for (int i = 1; i <= n; i++) {
        while (!st.empty() && a[st.top()] >= a[i]) st.pop();
        x[i] = st.empty() ? 1 : st.top()+1, st.emplace(i);
    }
    while (!st.empty()) st.pop();
    for (int i = n; i >= 1; i--) {
        while (!st.empty() && a[st.top()] > a[i]) st.pop();
        y[i] = st.empty() ? n : st.top()-1, st.emplace(i);
    }
    for (int i = 1; i <= n; i++) add(x[i], i, i, y[i], -a[i]);
}

using info_t = tuple<i64, i64, int>;
using tag_t = tuple<i64, i64, i64>;
const tag_t I{};
info_t operator+ (const info_t& u, const info_t& v) {
    auto [a1, b1, l1] = u; auto [a2, b2, l2] = v;
    return {a1 + a2, b1 + b2, l1 + l2};
}
tag_t operator* (const tag_t& u, const tag_t& v) {
    auto [x1, y1, z1] = u; auto [x2, y2, z2] = v;
    return {x1 + x2, y1 + y2, y1 * x2 + z1 + z2}; 
}
info_t operator* (const tag_t& u, const info_t& v) {
    auto [x, y, z] = u; auto [a, b, l] = v;
    return {a + x * l, y * a + b + z * l, l};
}
struct segtree {
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    info_t val[N << 2];
    tag_t tag[N << 2];
    void pushup(int rt) {val[rt] = val[ls] + val[rs];}
    void apply(int rt, const tag_t& c) {val[rt] = c * val[rt], tag[rt] = c * tag[rt];}
    void pushdown(int rt) {
        if (tag[rt] == I) return;
        apply(ls, tag[rt]), apply(rs, tag[rt]), tag[rt] = I;
    }
    void build(int l = 1, int r = n, int rt = 1) {
        if (l == r) return val[rt] = info_t(0, 0, 1), void();
        int mid = (l + r) >> 1;
        build(l, mid, ls), build(mid + 1, r, rs), pushup(rt);
    }
    void update(int tl, int tr, const tag_t& c, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return apply(rt, c), void();
        int mid = (l + r) >> 1; pushdown(rt);
        if (tl <= mid) update(tl, tr, c, l, mid, ls);
        if (tr > mid) update(tl, tr, c, mid + 1, r, rs);
        pushup(rt);
    }
    info_t ask(int tl, int tr, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return val[rt];
        int mid = (l + r) >> 1; pushdown(rt);
        if (tl <= mid && mid < tr) return ask(tl, tr, l, mid, ls) + ask(tl, tr, mid + 1, r, rs);
        return tl <= mid ? ask(tl, tr, l, mid, ls) : ask(tl, tr, mid + 1, r, rs);
    }
} T;

void _main() {
    prework(); cin >> m;
    for (int i = 1, l1, r1, l2, r2; i <= m; i++) {
        cin >> l1 >> r1 >> l2 >> r2;
        Q[r1].emplace_back(l2, r2, 1, i);
        if (l1 > 1) Q[l1-1].emplace_back(l2, r2, -1, i);
    }
    T.build();
    for (int x = 1; x <= n; x++) {
        for (auto [l, r, v] : U[x]) T.update(l, r, info_t(v, 0, 0));  // op 1 += v        
        T.update(1, n, info_t(0, 1, 0));   // op 2
        for (auto [l, r, t, id] : Q[x]) ans[id] += $1(T.ask(l, r)) * t;
    }
    for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
}

:::::

P8868 [NOIP2022] 比赛

给定长为 \(n\) 的序列 \(a_i,b_i\)\(q\) 次询问给定 \(x,y\),查询

\[\left(\sum_{[l,r] \subseteq [x,y]} \left(\max_{i \in [l, r]} a_i\right) \times \left(\max_{i \in [l, r]} b_i\right)\right) \bmod 2^{64} \]

\(n,q \le 2.5 \times 10^5\),2 秒,512 MB。

\(a\) 单调栈求出 \(a_i\) 为最大值的极长区间 \([u_i,v_i]\),同理对 \(b\) 求得 \(b_i\) 为最大值的极长区间 \([\alpha_i,\beta_i]\)

考察每对 \(a_i \times b_j\) 的贡献。可以发现 \(a_i, b_j\) 同时成为区间 \(\max\) 当且仅当 \(l \in [u_i,i] \cap [\alpha_i,i], r \in [i,v_i] \cap [i,\beta_i]\)。转 \((l,r)\) 二维平面分析,则 \((i,j)\) 的贡献区域是 \([u_i,i] \times [i,v_i]\)\([\alpha_i,i] \times [i,\beta_i]\) 两个 4-side 矩形的交集。询问是对 \([x,y] \times [x,y]\) 的直角三角形求和。

\(r\) 扫描线,在时刻 \(r\) 动态维护两个数组 \(f_l= \max \limits_{i=l}^r a_i\)\(g_l=\max \limits_{i=l}^r b_i\),点 \((l,r)\) 的权值即为 \(f_l \times g_l\)。考察 \(r-1\to r\)\(f\) 的变化:

  • \([l, r] \subseteq [u_r, r]\)\(f_l\) 变为 \(a_r\)
  • \(l<u_r\)\(f_l\) 不变。

对数组 \(g\) 同理,故修改是两次区间覆盖。

考虑在位置 \(l\) 处维护 \(f_l \times g_l\) 的历史和 \(h_l\),表示时刻 \(1 \sim r\) 每行点权的累加,则 \(y=r\) 的询问答案即为 \(\sum \limits_{i=x}^y h_i\)

上图展示了这个做法的几何意义,我们用若干垂直线拼出询问的三角形,这些垂直线段就是历史和。

至此转化为如下数据结构问题:

维护三个长为 \(n\) 的序列 \(a_i,b_i,c_i\),支持如下操作:

  • 给定 \(l,r,v\),对 \(i \in [l,r]\)\(a_i \gets v\)
  • 给定 \(l,r,v\),对 \(i \in [l,r]\)\(b_i \gets v\)
  • \(i \in [1,n]\)\(c_i \gets c_i+a_i\times b_i\)
  • 给定 \(l,r\) 查询 \(\sum \limits_{i=l}^r c_i\)

容易发现 \(c_i\)\(a_ib_i\) 的历史和。考虑构造半群信息。

将信息构造为向量 \(\begin{bmatrix} \sum a_i \\ \sum b_i \\ \sum a_ib_i \\ \sum c_i \\ \text{len} \end{bmatrix}\),其中 \(\text{len}\) 为区间长度。对操作进行讨论:

  • 序列 \(a_i\) 赋值 \(v\)\(\begin{bmatrix} 0 & 0 & 0 & 0 & v \\ 0 & 1 & 0 & 0 & 0 \\ 0 & v & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \sum a_i \\ \sum b_i \\ \sum a_ib_i \\ \sum c_i \\ \text{len} \end{bmatrix}=\begin{bmatrix} \sum v \\ \sum b_i \\ \sum vb_i \\ \sum c_i \\ \text{len} \end{bmatrix}\)
  • 序列 \(b_i\) 赋值 \(v\)\(\begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & v \\ v & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \sum a_i \\ \sum b_i \\ \sum a_ib_i \\ \sum c_i \\ \text{len} \end{bmatrix}=\begin{bmatrix} \sum a_i \\ \sum v \\ \sum va_i \\ \sum c_i \\ \text{len} \end{bmatrix}\)
  • 更新历史和:\(\begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} \sum a_i \\ \sum b_i \\ \sum a_ib_i \\ \sum c_i \\ \text{len} \end{bmatrix}=\begin{bmatrix} \sum a_i \\ \sum b_i \\ \sum a_ib_i \\ \sum c_i+\sum a_ib_i \\ \text{len} \end{bmatrix}\)

进行半群优化。观察复合后的矩阵结构,考虑只维护形如 \(\begin{bmatrix} a_{0,0} & 0 & 0 & 0 & a_{0,4} \\ 0 & a_{1,1} & 0 & 0 & a_{1,4} \\ a_{2,0} & a_{2,1} & a_{2,2} & 0 & a_{2,4} \\ a_{3,0} & a_{3,1} & a_{3,2} & 1 & a_{3,4}\\ 0 & 0 & 0 & 0 &1 \end{bmatrix}\) 的标记,这样只用维护 \(12\) 个自变量。

\(n,q\) 同阶复杂度 \(O(n \log n)\)

:::::info[代码]

const int N = 2.5e5 + 5;
int n, m, a[N], b[N], la[N], lb[N]; u64 ans[N];
vector<pair<int, int>> Q[N];  // (l, id)
void prework() {
    stack<int> st;
    for (int i = 1; i <= n; i++) {
        while (!st.empty() && a[st.top()] < a[i]) st.pop();
        la[i] = st.empty() ? 1 : st.top()+1, st.emplace(i);
    }
    while (!st.empty()) st.pop();
    for (int i = 1; i <= n; i++) {
        while (!st.empty() && b[st.top()] < b[i]) st.pop();
        lb[i] = st.empty() ? 1 : st.top()+1, st.emplace(i);
    }
}

using info_t = tuple<u64, u64, u64, u64, u64>;
using tag_t = tuple<u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64>;
const tag_t I(1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0);
info_t operator+ (const info_t& u, const info_t& v) {
    auto [a1, b1, ab1, c1, l1] = u;
    auto [a2, b2, ab2, c2, l2] = v;
    return {a1 + a2, b1 + b2, ab1 + ab2, c1 + c2, l1 + l2};
}
tag_t operator* (const tag_t& u, const tag_t& v) {
    auto [a00, a04, a11, a14, a20, a21, a22, a24, a30, a31, a32, a34] = u;
    auto [b00, b04, b11, b14, b20, b21, b22, b24, b30, b31, b32, b34] = v;
    return {
        a00 * b00, 
        a00 * b04 + a04,
        a11 * b11, 
        a11 * b14 + a14,
        a20 * b00 + a22 * b20, 
        a21 * b11 + a22 * b21, 
        a22 * b22,
        a20 * b04 + a21 * b14 + a22 * b24 + a24,
        a30 * b00 + a32 * b20 + b30,
        a31 * b11 + a32 * b21 + b31,
        a32 * b22 + b32,
        a30 * b04 + a31 * b14 + a32 * b24 + a34 + b34
    };
}
info_t operator* (const tag_t& u, const info_t& v) {
    auto [a00, a04, a11, a14, a20, a21, a22, a24, a30, a31, a32, a34] = u;
    auto [a, b, ab, c, l] = v;
    return {
        a00 * a + a04 * l,
        a11 * b + a14 * l,
        a20 * a + a21 * b + a22 * ab + a24 * l,
        a30 * a + a31 * b + a32 * ab + c + a34 * l,
        l
    };
}
struct segtree {
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    info_t val[N << 2];
    tag_t tag[N << 2];
    void pushup(int rt) {val[rt] = val[ls] + val[rs];}
    void apply(int rt, const tag_t& c) {val[rt] = c * val[rt], tag[rt] = c * tag[rt];}
    void pushdown(int rt) {
        if (tag[rt] == I) return;
        apply(ls, tag[rt]), apply(rs, tag[rt]), tag[rt] = I;
    }
    void build(int l = 1, int r = n, int rt = 1) {
        if (l == r) return val[rt] = info_t(0, 0, 0, 0, 1), void();
        int mid = (l + r) >> 1;
        build(l, mid, ls), build(mid + 1, r, rs), pushup(rt);
    }
    void update(int tl, int tr, const tag_t& c, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return apply(rt, c), void();
        int mid = (l + r) >> 1; pushdown(rt);
        if (tl <= mid) update(tl, tr, c, l, mid, ls);
        if (tr > mid) update(tl, tr, c, mid + 1, r, rs);
        pushup(rt);
    }
    info_t ask(int tl, int tr, int l = 1, int r = n, int rt = 1) {
        if (tl <= l && r <= tr) return val[rt];
        int mid = (l + r) >> 1; pushdown(rt);
        if (tl <= mid && mid < tr) return ask(tl, tr, l, mid, ls) + ask(tl, tr, mid + 1, r, rs);
        return tl <= mid ? ask(tl, tr, l, mid, ls) : ask(tl, tr, mid + 1, r, rs);
    }
} T;

void _main() {
    cin >> n >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    prework(), cin >> m;
    for (int i = 1, l, r; i <= m; i++) {
        cin >> l >> r;
        Q[r].emplace_back(l, i);
    }
    T.build();
    for (int r = 1; r <= n; r++) {
        T.update(la[r], r, tag_t(0, a[r], 1, 0, 0, a[r], 0, 0, 0, 0, 0, 0));  // op 1
        T.update(lb[r], r, tag_t(1, 0, 0, b[r], b[r], 0, 0, 0, 0, 0, 0, 0));  // op 2
        T.update(1, n, tag_t(1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0));  // op 3
        for (auto [l, id] : Q[r]) ans[id] += $3(T.ask(l, r));
    }
    for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
}

:::::

CF1824D LuoTianyi and the Function

给定长为 \(n\) 的序列 \(a_i\),定义函数 \(f(l,r)\)

  • \(l \le r\),则 \(f(l,r)\) 是满足 \(\{a_i \mid i \in [l,r]\}\subseteq \{a_j \mid [x,r]\}\) 的最大的 \(x\)
  • \(l>r\),则 \(f(l,r)=0\)

\(q\) 次询问,给定 \(l_1,r_1,l_2,r_2\),查询

\[\sum_{l \in [l_1,r_1]} \sum_{r \in [l_2,r_2]} f(l,r) \]

\(n, q \le 10^6\)7 秒,1000 MB。

CF1787I Treasure Hunt

posted @ 2026-08-01 17:29  stripe_python  阅读(0)  评论(0)    收藏  举报