聪明的质检员——二分、前缀和

题目描述

输入格式

输出格式

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

输入输出样例

输入 #1

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

输出 #1

10

说明

思路

题意:

  • 给定n个矿石,从1~n对它们进行编号,每个矿石都有一个重量w和价值v, 并给定一个标准值s
  • 有一个参数W
  • 给定m个区间,找到每个区间中重量大于参数W的矿石,y = 区间中符合条件的矿石数量*符合条件的矿石价值之和,将每个区间的y值相加得到Y
  • 找到一个参数W,可以使|Y - s|最小,也就是Y最接近s

1、二分

  • 一共有n个矿石,每个矿石都有一个重量w
  • 参数W小于矿石中重量最小值,那么每一个区间中所有的矿石都符合条件
  • 参数W大于矿石中重量最大值,那么每一个区间中的矿石都不符合条件,Y的结果就是0
  1. Y > s:减小参数W的值
  2. Y < s:增大参数W的值
  3. Y = s:|Y-s| = 0

2、前缀和

  • 每次利用二分找到一个参数W的时候,定义两个数组
  1. tw[i]:统计矿石重量大于参数W的数量之和
  2. tv[i]:统计矿石重量大于参数W的价值之和
  3. 由于区间数量较多,使用前缀和对数组进行预处理

代码实现

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;
typedef pair<int , int> PII;

const int N = 2e5 + 10;

int n, m;		// n个矿石,m个区间
LL s;	// 标准值
int wi[N], vi[N];	// 每个矿石的重量和价值
PII lr[N];	// 存储每个区间的左右端点

LL getSum(int w)
{
	LL tw[N], tv[N];		// 矿石数量和价值的前缀和数组
	memset(tw, 0, sizeof tw);
	memset(tv, 0, sizeof tv);
	for(int i = 1; i <= n; i++)
	{
		if(wi[i] >= w) tw[i] = 1 + tw[i-1], tv[i] = vi[i] + tv[i-1];	// 当前矿石符合条件
		else tw[i] = tw[i-1], tv[i] = tv[i-1];	// 当前矿石不符合条件,不用加上当前矿石数量和价值
	}
	LL sum = 0;	// sum表示每个区间的y值相加,就是Y
	for(int i = 1;i <= m; i++)	// 遍历每一个区间
	{
		PII p = lr[i];
		sum += (tw[p.second] - tw[p.first-1]) * (tv[p.second] - tv[p.first-1]);	// 利用前缀和的性质, 数量 * 价值之和
	}
	
	return sum;
}


int main()
{
	cin >> n >> m >> s;
	int minw = 0x3f3f3f3f, maxw = -1;	// minw表示重量最大值,minw表示重量最小值
	for(int i = 1;i <= n; i++)
	{
		cin >> wi[i] >> vi[i];
		if(wi[i] < minw) minw = wi[i];
		if(wi[i] > maxw) maxw = wi[i];
	}
	for(int i = 1;i <= m; i++) 
	{
		int l, r;
		cin >> l >> r;
		lr[i] = {l, r};
	}
	int l = minw - 1, r = maxw + 2;		// 二分的左右范围,因为W = maxw + 1的结果Y = 0,也是一种情况
	LL sum = 0, res = 0x3f3f3f3f3f3f3f3f;	// sum表示在当前参数W下,Y的值, res维护的是|Y-s|的最小值
	while(l <= r)
	{
		int mid = (l + r >> 1);
		sum = getSum(mid);		// 求Y的函数
		if(sum > s) l = mid + 1;	// Y > s
		else r = mid - 1;	// Y <= s
		sum = llabs(sum - s);	// 求|Y-s|的值
		if(sum < res) res = sum;	// 小于res的话,进行更新
	}
	
	cout << res << endl;
	
	return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值