AcWing 499. 聪明的质监员

本文介绍了如何结合前缀和与二分查找算法解决一类区间最优化问题,具体涉及计算满足特定条件的产品子集的总价值,并找到使绝对误差最小的解。通过实例解析了题意,阐述了二分查找的边界收缩策略,并提供了使用前缀和优化查询效率的代码实现。
摘要由CSDN通过智能技术生成

题目链接

关于套路

涉及到区间的,想都不用想,前缀和。然后再涉及一个找xxx的最小值的,想都不用想,二分。所以这题就是一道前缀和+二分的题。但是题目在说啥我好像没看明白?

题意

题意是这样的:个你n个产品的质量w和价值v,依次编号1-n

然后再给你m个查询l,r,其中l是最左边的产品编号,r是最右边产品的编号。比如产品编号有1,2,3,4,5。如果[l,r] = [1,5]的话,就是让你从编号1-5的产品中找满足某个条件的产品出来

再来解释一下 Y i = ∑ j 1 ∑ j v j Y_i= \sum_j1 \sum_jv_j Yi=j1jvj 。这公式说白了就是让你求区间i内,先找出所有满足w >= W的产品,最后 Y i Y_i Yi =产品个数 ∗ * 这些产品的总价值。最后统计所有区间的 Y i Y_i Yi之和 Y Y Y,使得 a b s ( Y − S ) abs(Y - S) abs(YS)最小。到这里终于明白他要干嘛了,题目的意思真是超级无敌好理解呢

二分

找这个W,按照套路一般是二分,题目给的w范围是 1 < = w < = 1 0 6 1 <= w <=10^6 1<=w<=106,那么二分的初始范围就是 [ 0 , 1 0 6 + 1 ] [0, 10^6+1] [0,106+1],(1和 1 0 6 10^6 106有可能都不是解)

再来考虑边界如何收缩,记当前左边界为l,右边界为r m i d = ( l + r ) / 2 mid = (l + r ) / 2 mid=(l+r)/2,假设W为mid。

  • Y ≤ S Y \leq S YS时,说明mid有可能是解,所以令r = mid,(mid越大的话,Y就会越小,所有右半舍弃)
  • Y > S Y > S Y>S时,说明mid不会是解,令l = mid + 1。(这个说法在边界处不成立,再看我后面的分析)

二分边界:考虑区间长度只有2的情况,即r = l + 1时,记 [ l , r ] [l,r] [lr],计算Y值的函数为getY, m i d = ( l + r ) / 2 = l mid = (l + r ) / 2 = l mid=(l+r)/2=l

假设到了最后,getY(l)大于S,getY( r )小于S。这时最优解的位置为l或者r

最优解在r的位置上的话 由于getY(mid) > S, 最终会令l = mid + 1 = 2结束二分, r正好是最优解
最优解在l的位置上的话 由于getY(mid) > S, 最终也是l = mid + 1 = 2结束,r-1才是最优解

所以最优解的位置是r或者r - 1

PS: 如果用$mid = (l + r + 1) / 2 $来计算,最优解的位置是rr + 1,可以自行推导哦

前缀和

对于每次二分,先用前缀和处理一下满足条件产品的个数和价值,最后用前缀和公式就可快速得到每个区间的 Y i Y_i Yi,累加就可得到Y,详情请看我的代码啦

代码

#include<iostream>
using namespace std;

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

const int N = 2e5 + 10, M = 1e6 + 10;

int w[N], v[N], cnt[N];
PII q[N];
LL s[N];
int n, m;
LL S;

LL getY(int mid)
{   
    //cnt[]计算产品个数,s[]计算产品的价值和
    for(int i = 1; i <= n; i++)
    {
        if(w[i] >= mid)
        {
            s[i] = v[i], cnt[i] = 1;
        }
        else{
            s[i] = 0, cnt[i] = 0;
        }
        s[i] += s[i - 1];
        cnt[i] += cnt[i - 1];
    }
    
    LL Y = 0;
    for(int i = 0; i < m; i++)
    {
        int x = q[i].first, y = q[i].second;
        Y += (cnt[y] - cnt[x - 1]) * (s[y] - s[x - 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 = 0; i < m; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        q[i] = {x, y};
    } 
    
    int l = 0, r = 1e6 + 1;
    while(l < r)
    {

        int mid = l + r >> 1;
        if(getY(mid) <= S) r = mid;
        else l = mid + 1;
    }
    
    printf("%lld", min(abs(S - getY(r)), abs(S - getY(r - 1))));
    
    return 0;
}   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值