题目链接:P1314 NOIP2011 提高组] 聪明的质监员 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
一、暴力枚举
参数 W 的范围为 全部矿石的重量都满足 到 全部矿石的重量都不满足 , 也就是 min( w[i] ) - 1 到 max( w[i] ) + 1 。
暴力枚举 参数 w,求解。
时间复杂度 O( W * m * n) 。 25 分
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10;
int w[MAXN], v[MAXN], l[MAXN], r[MAXN];
int n, m;
//给定W, 求出检验结果 y,并且返回
long long work(int W) {
long long y = 0;
for(int i = 1; i <= m; ++ i) {
long long cnt = 0, sum = 0;
for(int j = l[i]; j <= r[i]; ++ j) {
if(w[j] >= W) {
cnt ++;
sum += v[j];
}
}
y += cnt * sum;
}
return y;
}
int main() {
long long s;
cin >> n >> m >> s;
int minn = 1e6, maxn = 0;
for(int i = 1; i <= n; ++ i) {
cin >> w[i] >> v[i];
maxn = max(w[i], maxn);
minn = min(w[i], minn);
}
for(int i = 1; i <= m; ++ i) {
cin >> l[i] >> r[i];
}
long long ans = s;
for(int W = minn - 1; W <= maxn + 1; ++ W) {
long long y = work(W);
ans = min(ans, abs(s - y));
}
cout << ans;
return 0;
}
二、二分优化
因为 检验结果 y 和 参数 w 是负相关,并且我们的目的是让 检验结果 y 尽可能的靠近 标准值 s
从而得出结论: w 和答案之间的关系也是相关的,可以二分 w 。
二分时条件分三种情况:
-
y == s ,这时已经找到最有结果,退出循环。
-
y > s ,y 需要变小来靠近 标准值 s ,因此我们需要 W 向上偏移。
-
y < s ,y 需要变大来靠近 标准值 s ,因此我们需要 W 向下偏移。
时间复杂度 O( log(W) * m * n) 。 70 分
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10;
int w[MAXN], v[MAXN], l[MAXN], r[MAXN];
int n, m;
long long work(int W) {
long long y = 0;
for(int i = 1; i <= m; ++ i) {
long long cnt = 0, sum = 0;
for(int j = l[i]; j <= r[i]; ++ j) {
if(w[j] >= W) {
cnt ++;
sum += v[j];
}
}
y += cnt * sum;
}
return y;
}
int main() {
long long s;
cin >> n >> m >> s;
int minn = 1e6, maxn = 0;
for(int i = 1; i <= n; ++ i) {
cin >> w[i] >> v[i];
maxn = max(w[i], maxn);
minn = min(w[i], minn);
}
for(int i = 1; i <= m; ++ i) {
cin >> l[i] >> r[i];
}
long long ans = s;
int l = minn - 1, r = maxn + 1;
// y 和 w 是 负相关
// 二分W的区间
while (l < r) {
int mid = (l + r) >> 1;
long long y = work(mid);
if(y == s) {
ans = 0;
break;
}
else if(y > s) { // y需要变小 ,W需要向上偏移
l = mid + 1;
}
else { // y需要变大,W需要向下偏移
r = mid;
}
ans = min(ans, abs(s - y));
}
cout << ans;
return 0;
}
二、二分优化 + 前缀和优化
我们发现每次求work,都需要 多次查询区间且,求区间中满足条件的矿石 的 v[i]和 w[i]个数,且区间可能重合或相互重叠,这时我们可以预处理前缀和,把查询从
O(m * n) 优化到 O(max(n, m)) 。
时间复杂度 log(W) * max(n, m) 。 100 分
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10;
int w[MAXN], v[MAXN], l[MAXN], r[MAXN];
long long sum[MAXN], cnt[MAXN];
int n, m;
long long work(int W) {
long long y = 0;
memset(sum, 0, sizeof(sum));
memset(cnt, 0, sizeof(cnt));
for(int i = 1; i <= n; ++ i) {
if(w[i] >= W) {
sum[i] = sum[i - 1] + v[i];
cnt[i] = cnt[i - 1] + 1;
} else {
sum[i] = sum[i - 1];
cnt[i] = cnt[i - 1];
}
}
for(int i = 1; i <= m; ++ i) {
y += (cnt[r[i]] - cnt[l[i] - 1]) * (sum[r[i]] - sum[l[i] - 1]);
}
return y;
}
int main() {
long long s;
cin >> n >> m >> s;
int minn = 1e6, maxn = 0;
for(int i = 1; i <= n; ++ i) {
cin >> w[i] >> v[i];
maxn = max(w[i], maxn);
minn = min(w[i], minn);
}
for(int i = 1; i <= m; ++ i) {
cin >> l[i] >> r[i];
}
long long ans = s;
int l = minn - 1, r = maxn + 1;
// y 和 w 是 负相关
while (l < r) {
int mid = (l + r) >> 1;
long long y = work(mid);
if(y == s) break;
else if(y > s) { // y大了 意味着 W小了
l = mid + 1;
} else { // y小了 意味着 W大了
r = mid;
}
ans = min(ans, abs(s - y));
}
cout << ans;
return 0;
}