『NOIP 2011』聪明的质监员(二分答案 + 前缀和)

题目链接

题目描述

小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 \(n\) 个矿石,从 \(1\)\(n\) 逐一编号,每个矿石都有自己的重量 \(w_i\) 以及价值 \(v_i\) 。检验矿产的流程是:

1 、给定m个区间\([L_i,R_i]\)

2 、选出一个参数\(W\)

3 、对于一个区间\([L_i,R_i]\),计算矿石在这个区间上的检验值\(Y_i\)

img

这批矿产的检验结果 \(Y\) 为各个区间的检验值之和。即:\(Y_1+Y_2...+Y_m\)

若这批矿产的检验结果与所给标准值 \(S\) 相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数 \(W\) 的值,让检验结果尽可能的靠近标准值\(S\),即使得\(S-Y\)的绝对值最小。请你帮忙求出这个最小值。



解题思路

题目要求参数 \(W\) 最小,我们可以考虑二分 \(W\) 的值。

二分后我们就要考虑怎么检验是否合法了。

因为 \(m\) 的最大值是 \(2e5\) 所以我们不可能暴力枚举每个区间。每个区间必须在 \(O(1)\) 的时间内处理出来。我们发现这个式子的含义就是区间内 \(w\) 大于等于 \(W\) 的矿石的个数称这些矿石的价值之和。

显然,我们可以在\(O(n)\)的复杂度内处理出来两个前缀和数组,一个表示个数,一个表示和。每个区间的查询的复杂度就被降低到了\(O(1)\),这样我们的总复杂度就是\(O(log_{玄学} \times (m+n))\)

脑子抽了,一开始还想着三分。。。。



代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=2000050;
ll n,m,s;
ll w[maxn],v[maxn],pre[maxn],prew[maxn];
int l[maxn],r[maxn];
inline ll check(ll x){
    for(register int i=1;i<=n;i++){
        pre[i]=prew[i]=0;
        if(w[i]>=x){
            pre[i]=1;
            prew[i]=v[i];
        }
        pre[i]+=pre[i-1];
        prew[i]+=prew[i-1];
    }
    ll ans=0;
    for(register int i=1;i<=m;i++){
        ll sum=pre[r[i]]-pre[l[i]-1];
        ll tot=prew[r[i]]-prew[l[i]-1];
        //cout<<pre[r[i]]<<' '<<pre[l[i]-1]<<' '<<prew[r[i]]<<' '<<prew[l[i]-1]<<endl;
        ans+=sum*tot;
    }
    return ans;
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&s);
    ll ma=0;
    for(register int i=1;i<=n;i++){
        scanf("%lld%lld",&w[i],&v[i]);
        ma=max(ma,w[i]);
    }
    for(register int i=1;i<=m;i++){
        scanf("%d%d",&l[i],&r[i]);
    }
    register ll l=0,r=999999999999999,ans=999999999999999;
    while(l<=r){
        register ll mid=(l+r)>>1;
        ll tmp=check(mid);
        //cout<<l<<' '<<r<<' '<<tmp<<endl;
        if(tmp>s){
            l=mid+1;
        }
        else r=mid-1;
        ans=min(ans,abs(tmp-s));
    }
    cout<<ans<<endl;
}

猜你喜欢

转载自www.cnblogs.com/Fang-Hao/p/9688001.html