P1314 [​ 提高组] 聪明的质监员题目解析

题目链接: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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值