题目大意:小 T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n 个矿石,从 1 到 n 逐一编号,每个矿石都有自己的重量 wi 以及价值 vi 。检验矿产的流程是:
1 、给定m 个区间[Li,Ri];
2 、选出一个参数 W;
3 、对于一个区间[Li,Ri],计算矿石在这个区间上的检验值Yi:
这批矿产的检验结果 Y 为各个区间的检验值之和。即:
若这批矿产的检验结果与所给标准值S 相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数W 的值,让检验结果尽可能的靠近标准值S,即使得S-Y 的绝对值最小。请你帮忙求出这个最小值。( 1 ≤ n,m ≤ 200,000,0 < wi, vi ≤ 10 6 ,0 < S ≤ 1012 ,1 ≤ Li ≤ Ri ≤ n)
题目分析:
这道题,主要是看公式看起来不容易懂;只要把公式看明白了,也就会了。
由题,如果我们每次把 W 增加,显然,每次得到的检验结果将会减少;因此,这道题的单调性已经很明显了。又因为数据规模属于 10 5 级的,所以我们就能想到二分查找法。
每次我们二分一个 W 值;但是在查找的时候,我们又不能直接暴力地去查找每个区间对应的答案(因为这样的复杂度是 O(mn*log(n)))的),所以我们需要知道如何快速查找每个矿石对答案的贡献。
注意到每个区间都会覆盖到一些矿石,这些矿石有一个特点:只要当前设定的 W 值小于矿石的 W j ,这个矿石一定会对最后的检验结果产生贡献;否则,它一定不会对结果有任何贡献。因此,有了这个性质,我们可以每次都求出所有重量大于 W j 的矿石的前缀和,同时也统计对应的价值的前缀和,然后每次区间相减即可得到当前 W 值对应的答案。
一句话题解:二分答案,每次统计区间符合条件的前缀和,根据答案与标准值的差值更新答案。
下面附上代码:
- #include<cstdio>
- #include<algorithm>
- using namespace std;
- typedef long long LL;
- const int MX=200005;
- struct Stone{
- int w,v;
- }stone[MX],pref[MX];
- int n,m,lf=1e9,rt,mid,ml[MX],mr[MX],cnt[MX];
- LL S,diff=1e18;
- bool check(int x){
- int tot=0;
- LL totv=0,ans=0;
- for (int i=1;i<=n;i++){
- pref[i].w=pref[i-1].w+(stone[i].w>=x);
- pref[i].v=pref[i-1].v+(stone[i].w>=x ? stone[i].v : 0);
- }
- for (int i=1;i<=m;i++){
- ans+=1LL*(pref[mr[i]].w-pref[ml[i]-1].w)*(pref[mr[i]].v-pref[ml[i]-1].v);
- }
- if (abs(ans-S)<diff)
- diff=abs(ans-S);
- return ans>S ? 1 : 0;
- }
- int main(){
- scanf(”%d%d%I64d”,&n,&m,&S);
- for (int i=1;i<=n;i++){
- scanf(”%d%d”,&stone[i].w,&stone[i].v);
- if (stone[i].w<lf) lf=stone[i].w;
- if (stone[i].w>rt) rt=stone[i].w;
- }
- for (int i=1;i<=m;i++){
- scanf(”%d%d”,ml+i,mr+i);
- }
- while (lf<rt){
- mid=(lf+rt)>>1;
- if (check(mid)){
- lf=mid+1;
- } else {
- rt=mid;
- }
- }
- check(mid+1);
- printf(”%I64d”,diff);
- return 0;
- }