运用前缀和和二分加检验解决 聪明的质检员

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n个矿石,从1到 n 逐一编号,每个矿石都有自己的重量wi以及价值vi。检验矿产的流程是:
1、给定m个区间[Li,Ri];
2、选出一个参数W;

3、对于一个区间[Li,Ri],计算矿石在这个区间上的检验值 Yi

Yi=∑j1∗∑jvjY_{i}=\sum_{j}{1}*\sum_{j}{v_{j}}Yi​=∑j​1∗∑j​vj​且wj≥W,j是矿石编号

这批矿产的检验结果Y为各个区间的检验值之和。即:Y=∑i=1mYiY=\sum_{i=1}^{m}{Y_{i}}Y=∑i=1m​Yi​
若这批矿产的检验结果与所给标准值S相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数W的值,让检验结果尽可能的靠近标准值S,即使得S-Y的绝对值最小。请你帮忙求出这个最小值。

输入描述:

第一行包含三个整数 n,m,S,分别表示矿石的个数、区间的个数和标准值。
接下来的n行,每行2个整数,中间用空格隔开,第i+1行表示i号矿石的重量wi和价值vi 。
接下来的m行,表示区间,每行2个整数,中间用空格隔开,第i+n+1行表示区间[Li,Ri]的两个端点Li和Ri。注意:不同区间可能重合或相互重叠

输出描述:

输出只有一行,包含一个整数,表示所求的最小值。

示例1

输入

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

输出

10

说明

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

备注:

 

对于 10%的数据,有 1 ≤ n,m ≤ 10; 

对于 30%的数据,有 1 ≤ n,m ≤ 500; 

对于 50%的数据,有 1 ≤ n,m ≤ 5,000;

对于 70%的数据,有 1 ≤ n,m ≤ 10,000;
对于 100%的数据,有 1 ≤ n,m ≤ 200,000,0 < wi, vi ≤ 106,0 < S ≤ 1012,1 ≤ Li ≤ Ri ≤ n

看到求最小值,并且考虑因为w不断增大,得到质检的结果会不断减小,具有单调性,考虑二分w值来不断接近s值。

因为这一题m和n的范围比较大,并且区间会重叠,如果每次都去对区间内进行判断和查找就比较容易超时,注意到这里对于每一个判断的·w,也就是二分的mid所对应的符合要求的矿物保准都是一样的,先对每一次判断,每一位前面符合要求的总长度和总价值进行预处理,实现前缀和,从而能够在后面对每个区间的判断能够直接根据左界和右界计算出答案,从而减少循环次数。

下面是程序:

#include<stdio.h>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll s;
int n, m;
int cfw[200010];
int cfv[200010];
struct Kw//矿物
{
	int v, w;
}kw[200010];
struct Jy//检验
{
	int l, r;
}jy[200010];
ll check(ll mid)//判断
{
	ll sum = 0;
	memset(cfw, 0,sizeof(cfv));
	memset(cfv, 0, sizeof(cfv));
	for (int i = 1; i <= n; i++)
	{
		cfw[i] = cfw[i - 1];
		cfv[i] = cfv[i - 1];
        //先承接前面的,cfw代表符合的货物长度前缀和,cfv表示符合条件的价值前缀和
		if (kw[i].w >= mid)cfw[i]++, cfv[i] += kw[i].v;
        //发现符合要求的就加一个货物长度,并把价值加入
	}
	for (int i = 1; i <= m; i++)
	{
		ll x = jy[i].l, y = jy[i].r;//也就是左边界和右边界
		sum += (cfw[y] - cfw[x - 1])*(cfv[y] - cfv[x - 1]);//前缀和
	}
	return sum;
}
int main()
{
	scanf("%d%d%lld", &n, &m, &s);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &kw[i].w, &kw[i].v);
	for (int i = 1; i <= m; i++)
		scanf("%d%d", &jy[i].l, &jy[i].r);
	ll l = 1, r = 1e6;//将l,r分别置为加大至和极小值
	while (l <= r)//二分w w变大,check(w)变小,不断接近s
	{
		ll mid = (l + r) / 2;
		if (check(mid) < s)r = mid - 1;
		else l = mid + 1;
	}
	ll ans = min(s - check(l), check(r) - s);//保险起见,两个都算好了
	printf("%lld\n", ans);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值