【洛谷 P1314 】[NOIP2011 提高组] 聪明的质检员

一、题目完整解读

1. 题意梳理

现有 n 个矿石,每个矿石有重量 $w_i$、价值 $v_i$;给出 m 个查询区间 $[l_i,r_i]$。

我们选定一个参数 W,对每个区间计算检验值 $y_i$:

$$
y_i=\left(\sum_{j=l_i}^{r_i}[w_j\ge W]\right) \times \left(\sum_{j=l_i}^{r_i}[w_j\ge W]v_j\right)
$$

其中 $[p]$ 是指示函数:条件成立返回 1,否则 0。

总检验值 $y=\sum_{i=1}^m y_i$。

给定标准值 s,要求找到合适的 W,使得 $|y-s|$ 最小,输出这个最小差值。

2. 样例拆解(输入 #1)

输入:

5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3

当 $W=4$ 时:

  • 重量≥4 的矿石:4 号、5 号
  • 区间 [1,5]:数量 = 2,总价值 = 10 → $y_1=2\times10=20$
  • 区间 [2,4]:数量 = 1,总价值 = 5 → $y_2=1\times5=5$
  • 区间 [3,3]:无矿石≥4 → $y_3=0$

总和 $y=20+5+0=25$,$|25-15|=10$,是所有 W 中最小差值,输出 10。

3. 数据范围与暴力缺陷

$n,m \le 2\times 10^5$,矿石重量最大 $10^6$

暴力思路:枚举所有W,每次遍历 n、m 计算 y,复杂度$O(10^6 \times (n+m))$,严重超时,必须优化。

二、核心解题思路:二分答案

1. 单调性分析(二分成立关键)

我们观察总检验值 y 和参数 W 的变化关系:

  • W 越大,满足 $w_j \ge W$ 的矿石越少
  • 区间内合格矿石数量、总价值都会减小
  • 乘积 $y_i$ 减小,总和 y 单调递减

单调性结论:$W \uparrow \implies y \downarrow$,函数单调,满足二分答案使用条件。

2. 二分目标

二分枚举 W 的取值(值域 $[0,10^6+1]$),每次二分中点 mid 作为候选 W,计算当前总 y:

  • 若 $y \le s$:当前 W 偏大,总检验值偏小,尝试缩小 W(往左找更小 W,让 y 变大靠近 s)
  • 若 $y > s$:当前 W 偏小,总检验值偏大,需要增大 W(往右找更大 W,让 y 变小靠近 s)

每次计算完 y 后,更新全局最小差值 $|y-s|$,二分结束后输出最小值。

3. 快速计算 y:双前缀和优化

每次 check 函数中,如果暴力遍历每个区间统计合格矿石,单次 check 复杂度 $O(n+m)$,总复杂度 $O((n+m)\log(maxW))$,完全可以通过 $2\times10^5$ 的数据。

我们维护两个前缀数组:

  • $sn[i]$:前 i 个矿石中,重量≥W 的矿石数量前缀和
  • $sv[i]$:前 i 个矿石中,重量≥W 的矿石总价值前缀和

区间 $[l,r]$ 内:

  • 合格数量 = $sn[r]-sn[l-1]$
  • 合格总价值 = $sv[r]-sv[l-1]$
  • 区间检验值 = 数量 × 总价值

累加所有区间得到总 y。

三、完整代码逐行详解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll; // 数据极大,必须开long long防溢出
const int N = 200010; // n,m上限2e5,数组开2e5+10

int n, m, w[N], v[N], l[N], r[N];
ll s, sn[N], sv[N], ans = 1e18; // ans初始无穷大,记录最小差值

// check函数:给定参数z(当前二分的W),计算总y,返回y<=s用于二分判断
bool check(int z) {
    memset(sn, 0, sizeof sn); // 每次重置前缀数组
    memset(sv, 0, sizeof sv);
    
    // 预处理数量、价值前缀和
    for (int i = 1; i <= n; i++) {
        if (w[i] >= z) { // 当前矿石合格
            sn[i] = sn[i-1] + 1;
            sv[i] = sv[i-1] + v[i];
        } else { // 不合格,继承前一位前缀和
            sn[i] = sn[i-1];
            sv[i] = sv[i-1];
        }
    }
    
    ll y = 0; // 总检验值,开long long!
    // 遍历所有区间,累加每个区间的y_i
    for (int i = 1; i <= m; i++) {
        ll cnt = sn[r[i]] - sn[l[i]-1];    // 区间合格矿石数
        ll val = sv[r[i]] - sv[l[i]-1];    // 区间合格矿石总价值
        y += cnt * val;                    // 区间检验值累加到总和
    }
    
    ans = min(ans, llabs(y - s)); // 更新全局最小差值
    return y <= s; // 二分判断:y小于等于标准值,说明W太大,往左二分
}

// 二分主函数,二分W值域 [0, 1e6+1]
ll find() {
    int l = 0, r = 1e6 + 1;
    while (l + 1 < r) { // 二分模板:左闭右开,最终l、r为相邻值
        int mid = l + r >> 1; // 等价于 (l+r)/2,位运算更快
        if (check(mid)) r = mid; // y<=s,W偏大,右边界左移
        else l = mid;           // y>s,W偏小,左边界右移
    }
    return ans; // 返回全程记录的最小|y-s|
}

int main() {
    ios::sync_with_stdio(false); // 大数据加速cin
    cin.tie(0);
    
    cin >> n >> m >> s;
    
    // 读入n个矿石重量、价值
    for (int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    
    // 读入m个查询区间
    for (int i = 1; i <= m; i++) {
        cin >> l[i] >> r[i];
    }
    
    cout << find(); // 输出最小差值
    return 0;
}

关键细节说明

  1. long long 强制使用:$n,m$ 均为 $2\times10^5$,每个区间乘积可达 $(2e5)\times(2e5 \times 1e9)$,数值会远超 int 范围,所有前缀和、总 y、s、差值都要用 long long,否则直接溢出 WA。

  2. 二分边界:W 最小可取 0(所有矿石都合格,y 最大),最大取 $10^6+1$(无矿石合格,y=0),覆盖全部可能取值。

  3. 二分循环条件 l+1<r:这是整数二分经典模板,循环结束后 l 和 r 是相邻两个数,覆盖所有候选 W,不会漏掉任何可能解。

  4. memset 重置前缀数组:每次二分的 W 不同,合格矿石集合变化,前缀数组必须清零重新计算。

  5. llabs 取绝对值:y 和 s 都是 long long,不能用 abs(仅支持 int),必须用 llabs 求长整型绝对值。请添加图片描述

四、算法复杂度分析

  • 二分次数:矿石重量值域 $10^6$,二分次数 $\log_2(10^6) \approx 20$ 次
  • 单次 check 函数:遍历 n 个矿石预处理前缀 $O(n)$,遍历 m 个区间计算总和 $O(m)$
  • 总时间复杂度:$O((n+m) \log(maxW))$,$20\times 4\times105=8\times106$,完全满足时间限制

五、易错点总结(考场避坑)

  1. 数据溢出:忘记把 y、sn、sv、ans 开 long long,90% 选手初次写会踩坑
  2. abs/llabs 混用:long long 数值求绝对值必须用 llabs
  3. 二分单调性搞反:误以为 W 越大 y 越大,二分左右边界更新写反
  4. 前缀和下标错误:区间 $[l,r]$ 要减 sn[l-1],错写成 sn[l] 会少统计左端点矿石
  5. 二分边界设置过小:r 只开到 1e6,漏掉 W=1e6+1 的情况
  6. 未每次重置前缀数组:memset 漏掉,前缀和残留上一轮数据导致答案错误

六、拓展思考

  1. 能否离散化优化二分值域? 矿石 w 的取值最多 n 种,可把所有 w 排序去重,二分离散化后的下标,二分次数略减少,对本题影响不大
  2. 有没有不用二分的做法? 直接枚举所有 w 复杂度太高,无更优线性做法,二分是本题最优标准解法
  3. 多组区间重叠会影响前缀和吗? 不会,前缀和是静态预处理数组,每个区间独立查询,重叠区间不干扰计算

七、题目总结

本题是 NOIP 提高组经典二分答案 + 前缀和模板题,核心考察两点:

  1. 识别变量单调性,确定二分答案解题框架
  2. 使用前缀和快速区间统计,把单次检验的复杂度从 $O(nm)$ 降到 $O(n+m)$

posted on 2026-06-18 20:42  5iCode  阅读(0)  评论(0)    收藏  举报

导航