【NOIP 二分答案 前缀和】JZOJ_3028 聪明的质监员

本文介绍了一种针对矿石检验值的优化算法,通过二分查找和前缀和技巧,实现了对参数的精确调整,以使矿石检验结果与标准值的差距最小。算法时间复杂度为O((N+M)logN),适用于处理大规模数据集。

题意

给出nnn个东西以及他们的重量和价值。计算这些矿石的检验值:
1、给出mmm个区间[li,ri][l_i,r_i][li,ri]
2、确定一个参数WWW
3、对于一个区间[li,ri][l_i,r_i][li,ri],计算矿石在这个区间上的检验值YiY_iYi:这个区间上所有重量大于等于WWW的矿石数目与它们的价值和的乘积。这批矿产的检验结果YYY为各个区间的检验值之和。
我们要调整这个参数使得检验结果与给出的标准值SSS相差尽量小。

思路

如果调整WWW,调高会使有些矿石无法选择,答案就会变小,调低就会使答案变大。
我们发现检验值具有单调性,然后可以愉快的想到二分。
因为WWW一定是这些矿石中的某个重量,所以我们可以利用这些矿石的重量进行二分。
对于二分到的答案,如果大于SSS,就把WWW调高,否则调小,不断逼近SSS,然后我们在每次求出的检验结果中取最小值。
对于求检验结果,朴素算法是枚举每个区间,再暴力判断,这样时间复杂度为O(N2)O(N^2)O(N2),显然会超时。我们可以利用前缀和的方法,把超过WWW的矿石的信息都加到前缀和里,这样子可以优化成O(2N)O(2N)O(2N)
最后时间复杂度为O((N+M)logN)O((N+M)logN)O((N+M)logN)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

struct node{
	int w, v;
}stone[200001];
int n, m;
int a[200001], l[200001], r[200001], pre_n[200001];
long long ans, s;
long long pre_v[200001];

long long check(int x) {
	for (int i = 1; i <= n; i++) {
		pre_v[i] = pre_v[i - 1] + (stone[i].w >= x ? stone[i].v : 0);
		pre_n[i] = pre_n[i - 1] + (stone[i].w >= x ? 1 : 0);
	}//利用前缀和求检验结果
	long long y = -s;
	for (int i = 1; i <= m; i++)
		y += (pre_v[r[i]] - pre_v[l[i] - 1]) * (pre_n[r[i]] - pre_n[l[i] - 1]);
	return y;
}

int main() {
	scanf("%d %d %lld", &n, &m, &s);
	for (int i = 1; i <= n; i++) {
		scanf("%d %d", &stone[i].w, &stone[i].v);
		a[i] = stone[i].w;
	}
	for (int i = 1; i <= m; i++)
		scanf("%d %d", &l[i], &r[i]);
	std::sort(a + 1, a + n + 1);
	int t = std::unique(a + 1, a + n + 1) - a - 1;
	int l1 = 1, r1 = t + 1, mid;
	ans = 1e18;
	while (l1 < r1) {
		mid = (l1 + r1) >> 1;
		long long y = check(a[mid]);
		if (y > 0) l1 = mid + 1;//往S逼近
		else r1 = mid, y = -y;
		ans = std::min(ans, y);
	}
	printf("%lld", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值