洛谷题单指南-前缀和差分与离散化-P1314 [NOIP2011 提高组] 聪明的质监员

原题链接:https://www.luogu.com.cn/problem/P1314

题意解读:计算m个检验值之和,计算与s差值绝对值的最小值。

解题思路:

1、首先要搞懂检验值是如何计算的

如上图,对于每一个区间的检验值yi,表示为:yi = "该区间重量>=W的矿石个数"  ✖️ "该区间>=W的矿石价值之和"

检验值y即所有yi(1<=i<=m)之和。

要快速计算l ~ r区间里>=W的矿石个数,以及l ~ r区间里>=W的矿石价值之和,可以借助前缀和。

2、W和检验值y的关系

W越大,符合>=W的矿石就越少,因此y值会越小,可以看出,W与y之间具有单调性,所以可以采用二分W来求y >= s的最小的y,进而计算|y-s|。

3、完整流程

第一步:二分W的值mid

第二步:针对W,计算前缀和数组sc、sv,sc[i]表示前i个矿石里重量>=W的个数,sv[i]表示前i个矿石里重量>=W的价值之和

第三步:计算检测值y,如果y>=s,往更大范围搜l = mid + 1,否则r = mid - 1

第四步:更新答案,这里要注意是计算|y-s|的最小值

当y>=s时,应该增大W,使得|y-s|更小

当y<s时,应该缩小W,使得|y-s|更小

因此,不管y>=s还是y<s,都应该更新一次答案。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 200005;

int n, m;
long long s, ans = 1e18;
int w[N], v[N], l[N], r[N];
long long sc[N], sv[N];

bool check(int W)
{
    //计算前缀和sc,sv
    for(int i = 1; i <= n; i++)
    {
        if(w[i] >= W)
        {
            sc[i] = sc[i - 1] + 1;
            sv[i] = sv[i - 1] + v[i];
        } 
        else
        {
            sc[i] = sc[i - 1];
            sv[i] = sv[i - 1];
        } 
    }
    //计算检验值y
    long long y = 0;
    for(int i = 1; i <= m; i++)
    {
        y += (sc[r[i]] - sc[l[i] - 1]) * ((sv[r[i]] - sv[l[i] - 1]));
    }
    //注意不管y>=s还是y<s,都应该更新答案
    ans = min(ans, abs(y - s));
    //判断检验值
    if(y >= s) return true;
    else return false;
}

int main()
{
    cin >> n >> m >> s;
    for(int i = 1; i <= n; i++) cin >> w[i] >> v[i];
    for(int i = 1; i <= m; i++) cin >> l[i] >> r[i];

    int left = 1, right = 1e9; 
    while(left <= right)
    {
        int mid = left + right >> 1;
        if(check(mid)) left = mid + 1;
        else right = mid - 1;
    }
    cout << ans;
    return 0;
}

 

posted @ 2024-07-25 17:38  五月江城  阅读(19)  评论(0编辑  收藏  举报