[NOIP2011 提高组] 聪明的质监员

[NOIP2011 提高组] 聪明的质监员 - 洛谷

题意分析

n 个物品,每件物品有自己的重量 w_{i} 和价值 v_{i} ,现在给定 m 个区间,一个参数 W 和一个标准值 s

已知:

y_{i}=\sum_{j=l_{i}}^{r_{i}}[w_{j}\geq W]\times \sum_{j=l_{i}}^{r_{i}}[w_{j}\geq W]v_{j}

 y =\sum_{i=1}^{m}y_{i}

现在我们想要调整 W 的值 ,使得\left | s-y \right |最小,求出这个最小值。

算法分析

题目中1\leq n,m\leq 200,000,时间卡的很紧,只能支持一维循环,可是我们还要找 W ,而且 W 范围为1\leq W\leq 10^{6},肯定是不能循环爆搜,那么就可以用二分查找,毕竟二分查找在int范围内也是常数的时间复杂度,再加上计算y的一维复杂度,可以做。

那么先来组织算y的代码:我们容易发现这个求和是在一个区间内所有满足条件的数的和,很容易想到前缀和,那么每次二分求一次前缀和,区间和就是sum[r_{i}]-sum[l_{i}-1](注意这个-1,蒟蒻作者甚至错了),y的因数分别求前缀和,算出y。

然后是二分,查找的范围很容易想到是w_{i}v_{i}的范围,所以设l=0r=1e6,在这个范围内进行二分查找W,再来证明一下单调性:由y的计算公式易得当W较大时,能取到的w_{j}v_{j}少,那么它们区间和的乘积自然小,由此可以推出该二分应是单调递减的。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int n,m;
long long s;
int w[N],v[N];
int l[N],r[N];
long long sw[N],sv[N];
long long ans=0x3f3f3f3f3f3f3f3f;
long long chk(int mid)
{
	memset(sw,0,sizeof(sw));
	memset(sv,0,sizeof(sv));
	for(int i=1;i<=n;++i)
	{
		if(w[i]>=mid)
		{
			sw[i]=sw[i-1]+1;
			sv[i]=sv[i-1]+v[i];
		}
		else
		{
			sw[i]=sw[i-1];
			sv[i]=sv[i-1];
		}
	}
	long long y=0;
	for(int i=1;i<=m;++i)
	{
		y+=(sw[r[i]]-sw[l[i]-1])*(sv[r[i]]-sv[l[i]-1]);
	}
	return y;
}
int main()
{
	scanf("%d%d%lld",&n,&m,&s);
	for(int i=1;i<=n;++i) scanf("%d%d",&w[i],&v[i]);
	for(int i=1;i<=m;++i) scanf("%d%d",&l[i],&r[i]);
	int lc=0,rc=1e6;
	while(lc<rc)
	{
		int mid=(lc+rc)/2;
		if(chk(mid)>s) lc=mid+1;
		else rc=mid;
		ans=min(abs(chk(mid)-s),ans);
	}
	printf("%lld",ans);
	return 0;
}

 后记

 本体的思路还是很简单的,代码其实也就是一个二分模板,但即便简单如此,也会有一些小坑,如果注意不到那就是满盘皆输。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值