NOIP2011 聪明的质检员 二分 前缀和

博客围绕矿石区间检验值问题展开,给定n个矿石及m个区间和预期标准值S,需选参数W使检验值和最接近S。采用二分法,利用其单调性确定边界修改方法。还引入前缀和简化区间查询,在O(1)复杂度内计算区间和,最后给出解题思路及AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:https://www.luogu.com.cn/problem/P1314
题目大意:现有n个矿石从左到右排列,每个矿石有自己的重量wiw_iwi和价值viv_ivi,现给定mmm个区间[li,ri][l_i,r_i][li,ri]和预期标准值SSS,我们需要选出一个参数WWW,使每个区间检验值yiy_iyi之和最接近预期标准值SSS(即作差的绝对值∣s−y∣|s-y|sy最小),检验值的计算方法如下:

对于一个区间而言:yi=∑j=liri[wj≥W]×∑j=liri[wj≥W]vjy_i=\sum\limits^{r_i}_{j=l_i}[w_j \geq W]\times\sum\limits^{r_i}_{j=l_i}[w_j \geq W]v_jyi=j=liri[wjW]×j=liri[wjW]vj
这个算式可以这样理解:一个区间中重量大于标准值的个数乘总价值。
(到这里前缀和求区间和的思路就比较明显了,先往下看)

数据范围如下图所示:
在这里插入图片描述
对于100%100\%100%的数据,需要我们用O(nlog⁡n)O(n\log n)O(nlogn)的算法,我们自然可以想到二分法。对于二分答案法,我们首先需要考虑单调性,我们可以知道,给出的矿石重量是一定的,当我们增加参数WWW的大小时,每个单调区间的满足wj≥Ww_j\geq WwjW的矿石数量与增加前一定减少或相等,那么每个区间的检验值一定减少或相等。我们可以有以下结论:

选取的参数越高,我们得到的检验结果yyy(即所有区间检验值之和)越小。这个具有单调性的结论说明了我们使用二分答案做法的合理性。

那么我们二分答案的边界修改应该如何操作呢?在一开始时,我们定义l=1l=1l=1r=+infr=+infr=+infmid=l+r>>1mid=l+r>>1mid=l+r>>1。在题目中我们需要尽量地靠近一个值sss,也就是尽可能地减小绝对值。
当基于W=midW=midW=mid的检测结果yyy的值比预期标准值sss大时,说明更优的答案应当在mid+1mid+1mid+1rrr之间,修改边界:l=mid+1l=mid+1l=mid+1,并比较WWW与当前最优答案ansansans的值。
当基于W=midW=midW=mid的检测结果yyy的值比预期标准值sss小时,说明更优的答案应当在lllmid−1mid-1mid1之间,修改边界:r=mid−1r=mid-1r=mid1,并比较WWW与当前最优答案ansansans的值。
y==sy==sy==s时,直接输出0然后结束程序。

这样我们的二分答案的思路就有了。考虑在O(N)O(N)O(N)的时间复杂度内计算出给定WWW时的检测结果yyy。我们进一步简化问题,在WWW已知的情况下,我们对与WWW有关的特殊数组进行m次区间查询。问题的重心就转移到如何在O(1)的时间复杂度计算出一个区间。此时我们就应该联想到一些特殊的数据处理方法,诸如前缀和,差分,树状数组,线段树等等,这里肯定不会大材小用后两个,后两个的区间查询复杂度也比较高。这里直接引入前缀和,为什么是前缀和呢?
在这里插入图片描述
假设当前有这样的一个原始数组。
在这里插入图片描述
我们做这样的处理后获得前缀和数组,该数组每个元素sum[i]=a[1]+a[2]+...+a[i]sum[i]=a[1]+a[2]+...+a[i]sum[i]=a[1]+a[2]+...+a[i]。这样处理的好处是什么呢?我们有了这个数组,就可以在O(1)的复杂度内求出原始数组的一个区间和。即a[i]+a[i+1]+a[i+2]+...+a[j−1]+a[j]=sum[j]−sum[i−1],(i<j)a[i]+a[i+1]+a[i+2]+...+a[j-1]+a[j]=sum[j]-sum[i-1], (i<j)a[i]+a[i+1]+a[i+2]+...+a[j1]+a[j]=sum[j]sum[i1],(i<j),便求出了原始数组中[i, j][i,\space j][i, j]的区间数值之和

我们观察检验值的计算公式,其中包含有两个未知项,分别是一个满足条件的数量区间和与一个满足条件的价值区间和,我们分两个数组itemsum[i]item_sum[i]itemsum[i]valuesum[i]value_sum[i]valuesum[i]分别储存两种前缀和。具体的处理方法是这样:

for(int i=1;i<=n;i++)
    {
        if(w[i]>=x)
        {
            value_sum[i]=value_sum[i-1]+1;
            item_sum[i]=item_sum[i-1]+v[i];
        }
        else
        {
            value_sum[i]=value_sum[i-1];
            item_sum[i]=item_sum[i-1];
        }
    }

可以看出,我们遍历了两个前缀和数组,若遍历到当前的矿石满足重量条件,则将他的值加入前缀和数组中,若不满足,则保持之前的前缀和数组不变。这样我们就完成了前缀和数组的初始化。
接下来就是查询操作,这一步很简单:

long long cal()
{
    long long ans=0;
    for(int i=1;i<=m;i++)
    {
        ans+=(item_sum[r[i]]-item_sum[l[i]-1])*(value_sum[r[i]]-value_sum[l[i]-1]);
    }
    return ans;
}

如上,每个区间的查询结果就是两个区间的前缀和相乘,把所有结果相加就是取当前参数WWW的检验值。记得开long longlong\space longlong long,不然会炸裂。计算结束后回到之前的二分思路,就完成解题了。

下面给出AC代码:

#include<bits/stdc++.h>
using namespace std;
int w[200200],v[200200];
int l[200200],r[200200];
int n,m;
long long s;
long long item_sum[200200],value_sum[200200];
int max_w=-1;
long long min(long long x,long long y)
{
    if(x>y) return y;
    else return x;
}
void modify(int x)
{
    for(int i=1;i<=n;i++)
    {
        if(w[i]>=x)
        {
            value_sum[i]=value_sum[i-1]+1;
            item_sum[i]=item_sum[i-1]+v[i];
        }
        else
        {
            value_sum[i]=value_sum[i-1];
            item_sum[i]=item_sum[i-1];
        }
    }
}
long long cal()
{
    long long ans=0;
    for(int i=1;i<=m;i++)
    {
        ans+=(item_sum[r[i]]-item_sum[l[i]-1])*(value_sum[r[i]]-value_sum[l[i]-1]);
    }
    return ans;
}
int main()
{
	cin>>n>>m>>s;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
		max_w=max(max_w,w[i]);
	}
	for(int i=1;i<=m;i++)
	{
		cin>>l[i]>>r[i];
	}
	int begin=1,end=max_w+1,mid;
	long long anss=9999999999999999;
	while(begin<=end)
	{
		mid=begin+end>>1;
		modify(mid);
		long long ans=cal();
		if(ans>=s)
		{
			begin=mid+1;
		}
		else
		{
		    end=mid-1;
		}
        anss=min(anss,fabs(s-ans));
	}
	cout<<anss;
 }

有疏漏和讲解不清的地方请私信留言,祝你新的一天RP++。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值