题目描述
小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 \(n\) 个矿石,从 \(1\) 到 \(n\) 逐一编号,每个矿石都有自己的重量 \(w_i\) 以及价值 \(v_i\) 。检验矿产的流程是:
1 、给定m个区间\([L_i,R_i]\);
2 、选出一个参数\(W\);
3 、对于一个区间\([L_i,R_i]\),计算矿石在这个区间上的检验值\(Y_i\):
这批矿产的检验结果 \(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;
}