聪明的质检员-洛谷-P1314

原题
小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 nn 个矿石,从 11到nn 逐一编号,每个矿石都有自己的重量wi 以及价值vi​。检验矿产的流程是:
1 、给m个区间(Li-Ri)
2 、选出一个参数 W;
3 、对于一个区间(Li-Ri) ,计算矿石在这个区间上的检验值Yi :
在这里插入图片描述
这批矿产的检验结果Y 为各个区间的检验值之和。即:y1+y2+…ym
若这批矿产的检验结果与所给标准值SS 相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数W 的值,让检验结果尽可能的靠近标准值S,即使得S-Y 的绝对值最小。请你帮忙求出这个最小值。
输入输出格式
输入格式:
第一行包含三个整数n,m,S分别表示矿石的个数、区间的个数和标准值。
接下来的n行,每行2个整数,中间用空格隔开,第i+1行表示ii号矿石的重量wi 和价值vi。
接下来的m 行,表示区间,每行2个整数,中间用空格隔开,第i+n+1行表示区间[L_i,R_i]的两个端点Li和Ri 。
注意:不同区间可能重合或相互重叠。
输出格式:
一个整数,表示所求的最小值。
输入输出样例
输入样例#1:
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
输出样例#1:
10
说明
【输入输出样例说明】
当W选4的时候,三个区间上检验值分别为 20,5 ,0 ,这批矿产的检验结果为 25,此时与标准值S相差最小为10。
【数据范围】
1 ≤n ,m≤200,000,0 < wi,vi≤10^6,0 < S≤10^12,1 ≤Li ≤Ri ≤n,m≤200,000,0。
题意:
有n块石头,每一个都有自己的重量wi和价值vi,现在给出m个区间,给出一个测评标准W,每一个区间内都能够按照题目中图片的方式求出一个Yi,最后计算所有Yi和为Y,同时给出一个标准值S,求怎样的W才能使Y-S最小,输出最小值。
题解:
首先上来的一个难题就是这个图片中的计算方式:
两个求和符号,其实就是先运算后面的求和符号再运算前面的求和符号。而对于这道题目来说,因为所有的上下限都一样,就相当于求出所有大于W的石头(假设总数为x)的价值之和,再加上x遍。也就是求出所有大于w的石头价值之和,并且乘以大于W的石头数量。
第二个问题就是二分了:
这道题目很明显要对W来二分,可以确定W的下限就是所有石头中重量最小的那一个减1,上限就是所有石头中重量最大的加二(加二是因为要考虑Y=0的情况)。判断函数只需要利用前缀和来优化时间,降低复杂度。
计算出Y后与s比较,若是大了,则证明W取小了,反之则取大了。并且每一次都记录y-s。最终答案为最小的y-s。
注意:每一次判断是都要对数据初始化(sv数组,sn数组,y,sum)。
附上AC代码:

#include <iostream>
#include <cstring>
using namespace std;
int n,m,w[2000005],v[2000005],l[200005],r[200005];
long long sv[200005],sn[200005];//前缀和sv用来储存前i个中重量大于W的价值之和,sn用来储存前i个里面有几个的重量大于W
long long s,y,sum;
bool check(int mid)
{
    y=sum=0;
    memset(sv,0,sizeof(sv));
    memset(sn,0,sizeof(sn));
    for(int i=1;i<=n;i++)
    {
        if(w[i]>=mid)//满足条件则加入前缀和里
        {
            sv[i]=sv[i-1]+v[i];
            sn[i]=sn[i-1]+1;
        }
        else
        {
            sv[i]=sv[i-1];
            sn[i]=sn[i-1];
        }
    }
    for(int i=1;i<=m;i++)
    {
        y+=(sn[r[i]]-sn[l[i]-1])*(sv[r[i]]-sv[l[i]-1]);//计算yi
    }
    sum=y-s;
    sum=sum>0?sum:-sum;//取绝对值
    if(y>s)return true;
    else return false;
}
int main()
{
    int le=1e7,mid,ri=0;
    cin>>n>>m>>s;
    for(int i=1;i<=n;++i)
    {
        cin>>w[i]>>v[i];
        le=le<w[i]?le:w[i];//得到所有石头中重量的最小值
        ri=ri>w[i]?ri:w[i];//得到所有石头中重量的最大值
    }
    for(int i=1;i<=m;++i)
    {
        cin>>l[i]>>r[i];
    }
    long long ans=1e13;
    le=le-1,ri=ri+2;//下限为重量中最小值减一,上限为重量中最大值加二,目的是为了能够包含最小值和最大值加一,
    //加二的原因是为了考虑到一个石头都没有取Y=0的情况
    while(le<=ri)
    {
        mid=(le+ri)/2;
        if(check(mid))
            le=mid+1;
        else
            ri=mid-1;
        if(ans>sum)ans=sum;//更新为所有y-s中绝对值最小的
    }
    cout <<ans<< endl;
    return 0;
}

欢迎评论!

猜你喜欢

转载自blog.csdn.net/wjl_zyl_1314/article/details/82889301