NC16597 [NOIP2011]聪明的质监员

题目

题目描述

小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n 个矿石,从 1n 逐一编号,每个矿石都有自己的重量 wi 以及价值 vi 。检验矿产的流程是:

1 、给定m 个区间 [li,ri]

2 、选出一个参数 W

3 、对于一个区间 [li,ri],计算矿石在这个区间上的检验值 yi

yi=j=liri[wjW]×j=liri[wjW]vj

其中 j 为矿石编号。

这批矿产的检验结果 y 为各个区间的检验值之和。即:i=1myi

若这批矿产的检验结果与所给标准值 s 相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 W 的值,让检验结果尽可能的靠近标准值 s,即使得 |sy| 最小。请你帮忙求出这个最小值。

输入格式

第一行包含三个整数 n,m,s,分别表示矿石的个数、区间的个数和标准值。

接下来的 n 行,每行两个整数,中间用空格隔开,第 i+1 行表示 i 号矿石的重量 wi 和价值 vi

接下来的 m 行,表示区间,每行两个整数,中间用空格隔开,第 i+n+1 行表示区间 [li,ri] 的两个端点 liri。注意:不同区间可能重合或相互重叠。

输出格式

一个整数,表示所求的最小值。

样例 #1

样例输入 #1

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

样例输出 #1

10

提示

【输入输出样例说明】

W4 的时候,三个区间上检验值分别为 20,5,0 ,这批矿产的检验结果为 25,此时与标准值 S 相差最小为 10

【数据范围】

对于 10 的数据,有 1n,m10

对于 30的数据,有 1n,m500

对于 50 的数据,有 1n,m5,000

对于 70% 的数据,有 1n,m10,000

对于 100% 的数据,有 1n,m200,0000<wi,vi1060<s10121lirin

题解

知识点:二分。

yi=j=liri[wjW]×j=liri[wjW]vj ,发现 yiW 增大而减小,所以 SYW 增大而增大,而答案就在 SY 的零点附近,可以二分 W ,得到最优的 W 来求得最优的 SY。由于答案并非严格零点,因此最终需要对零点两侧都要计算一遍取最合适的值。

然后,预处理出 [wiW] 的前缀和 cnt[i][wiW]vi 的前缀和 vsum[i] ,能优化复杂度。

于是有 yi=(cnt[ri]cnt[li1])×(vsum[ri]vsum[li1]) ,得到 Y 返回 SY 的正负情况。

时间复杂度 O(n+m)

空间复杂度 O(n+m)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n, m;
ll s;
int w[200007], v[200007];
ll cnt[200007], vsum[200007];
ll ans = ~(1LL << 63);
struct area {
int l, r;
}a[200007];
ll check(int mid) {
for (int i = 1;i <= n;i++) {
cnt[i] = cnt[i - 1] + (w[i] >= mid);
vsum[i] = vsum[i - 1] + (w[i] >= mid) * v[i];
}
ll y = 0;
for (int i = 1;i <= m;i++) {
y += (cnt[a[i].r] - cnt[a[i].l - 1]) * (vsum[a[i].r] - vsum[a[i].l - 1]);
}
ans = min(ans, abs(s - y));///记录最小值,收敛点附近两个点的值刚好能被mid经过
return s - y >= 0;
/* check(mid)代表W=mid的s-y 是否>= 0
若是,则W需要变小;若否,则W需要变大
Y随W增大而减少, 但增量未知,而我们要求的是|s-y|而非W, 所以需要记录收敛点(变号点)左右两点的最小值 */
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m >> s;
for (int i = 1;i <= n;i++) cin >> w[i] >> v[i];
for (int i = 1;i <= m;i++) cin >> a[i].l >> a[i].r;
int l = 1, r = 1e6;
while (l <= r) { ///二分W,因为Y(W)单调减,找到s-y的零点附近即可
int mid = l + r >> 1;
if (check(mid)) r = mid - 1;
else l = mid + 1;
}
cout << ans << '\n';
return 0;
}
posted @   空白菌  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示