1 条题解

  • 0
    @ 2025-10-24 11:33:55

    题目理解

    我们有一个长度为 nn 的序列 aa,初始全为 00

    nn 个操作 (xi,yi)(x_i, y_i),表示将 a1a_1axia_{x_i} 都加上 yiy_i

    qq 次查询,每次给出 [l,r][l, r],问依次执行第 ll 到第 rr 个操作后,序列的最大值是多少。


    问题分析

    关键观察

    执行操作 (x,y)(x, y) 后,序列的前缀和变化:

    • 位置 11xx+y+y
    • 位置 x+1x+1nn:不变

    因此,最终 aia_i 的值等于所有覆盖了位置 ii 的操作的 yy 值之和。

    即:

    ai=j=lr[xji]yja_i = \sum_{j=l}^{r} [x_j \ge i] \cdot y_j

    我们要找:

    $$\max_{i=1}^n a_i = \max_{i=1}^n \sum_{j=l}^{r} [x_j \ge i] \cdot y_j $$

    核心思路

    1. 问题转化

    Si=j=lr[xji]yjS_i = \sum_{j=l}^{r} [x_j \ge i] \cdot y_j,则:

    • S1S_1 包含所有操作(因为 xj1x_j \ge 1 对所有 jj 成立)
    • Si+1=Sij=lr[xj=i]yjS_{i+1} = S_i - \sum_{j=l}^{r} [x_j = i] \cdot y_j

    所以 SiS_i 是一个前缀和递减的序列。

    我们要找的就是 maxi=1nSi\max_{i=1}^n S_i

    2. 数据结构设计

    struct info_t {
        ll mx, sum;
    };
    
    • mx:该区间操作序列产生的最大值
    • sum:该区间操作序列对 S1S_1 的贡献(即所有 yy 之和)

    合并规则:

    info_t unite(const info_t &lhs, const info_t &rhs) {
        return {max(lhs.mx + rhs.sum, rhs.mx), lhs.sum + rhs.sum};
    }
    

    这表示:

    • 总最大值 = max(左区间最大值 + 右区间总和, 右区间最大值)
    • 总和 = 左区间总和 + 右区间总和

    3. 分块处理

    代码将操作序列分成大小为 512512 的块,对每个块预处理信息。

    对于每个块:

    • xix_i 排序操作
    • 预处理出对于任意区间 [l,r][l, r] 在该块内的答案

    算法流程详解

    1. 预处理阶段

    sort(id + 1, id + n + 1, [](int p, int q) {
        return a[p] < a[q] || (a[p] == a[q] && b[p] > b[q]);
    });
    

    xix_i 排序,xix_i 相同时按 yiy_i 降序排列。

    2. 线段树构建

    void build(int p, int l, int r) {
        info[p].assign(r - l + 2, vector<info_t>(r - l + 2, {-infll, 0}));
        // 为每个节点分配存储空间
    }
    

    3. 关键操作:pushup

    void pushup(int p) {
        int lp = ls(p), rp = rs(p);
        // 合并左右子节点的关键点(x值)
        mp[p].resize(mp[lp].size() + mp[rp].size() - 1);
        merge(mp[lp].begin(), mp[lp].end() - 1,
              mp[rp].begin(), mp[rp].end() - 1, mp[p].begin());
        
        // 预处理指针数组,用于快速查找
        for (int i = 0, li = 0, ri = 0; i < (int)mp[p].size(); ++i) {
            while (li < (int)mp[lp].size() && mp[lp][li] < mp[p][i]) ++li;
            while (ri < (int)mp[rp].size() && mp[rp][ri] < mp[p][i]) ++ri;
            psl[i] = li, psr[i] = ri;
        }
        
        // 预处理所有区间组合的答案
        for (int i = 0; i < (int)mp[p].size(); ++i) {
            for (int j = i + 1; j < (int)mp[p].size(); ++j) {
                info[p][i][j] = unite(info[lp][psl[i]][psl[j]],
                                      info[rp][psr[i]][psr[j]]);
            }
        }
    }
    

    4. 查询处理

    对于每个查询 [l,r][l, r]

    for (int i = 1; i <= n; i += 512) {
        update(i);  // 更新当前块的信息
        for (int j = 1; j <= q; ++j) {
            ans[j] = unite(ans[j], info[1][pos[l]][pos[r + 1]]);
        }
    }
    

    样例分析

    对于样例输入:

    6 5
    6 4
    2 6  
    5 -5
    3 6
    1 2
    3 6
    1 6
    1 6
    2 6
    2 6
    5 6
    

    操作序列:

    1. (6,4)
    2. (2,6)
    3. (5,-5)
    4. (3,6)
    5. (1,2)
    6. (3,6)

    查询 [1,6] 的计算:

    • S1=4+6+(5)+6+2+6=19S_1 = 4+6+(-5)+6+2+6 = 19
    • S2=190=19S_2 = 19 - 0 = 19(没有x=1x=1的操作)
    • S3=196=13S_3 = 19 - 6 = 13(操作2的x=2x=2
    • S4=136=7S_4 = 13 - 6 = 7(操作4,6的x=3x=3
    • S5=7(5)=12S_5 = 7 - (-5) = 12(操作3的x=5x=5
    • S6=124=8S_6 = 12 - 4 = 8(操作1的x=6x=6

    最大值为 1919,与输出一致。


    复杂度分析

    • 预处理O(nlogn+nBB2)=O(nB)O(n \log n + \frac{n}{B} \cdot B^2) = O(nB),其中 B=512B=512
    • 查询O(qnB)O(q \cdot \frac{n}{B})
    • 总复杂度O(nB+nqB)O(nB + \frac{nq}{B}),取 B=nB=\sqrt{n} 时为 O(nn)O(n\sqrt{n})

    对于 n,q5×105n,q \le 5\times 10^5,可接受。


    算法标签

    • 分块
    • 线段树
    • 离线查询
    • 前缀和优化
    • 区间最值

    总结

    这道题的解法核心是:

    1. 问题转化:发现序列最大值等于某个前缀和序列的最大值
    2. 分块处理:将操作序列分块,预处理每个块的信息
    3. 高效合并:设计合适的数据结构支持区间信息快速合并
    4. 边界处理:正确处理不完整块的情况

    通过分块平衡了预处理和查询的复杂度,实现了高效求解。

    • 1

    信息

    ID
    4015
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    10
    已通过
    1
    上传者