NOIP2011 聪明的质检员

题目大意:小 T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n 个矿石,从 1 到 n 逐一编号,每个矿石都有自己的重量 wi 以及价值 vi 。检验矿产的流程是:

1 、给定m 个区间[Li,Ri];
2 、选出一个参数 W;
3 、对于一个区间[Li,Ri],计算矿石在这个区间上的检验值Yi:

这批矿产的检验结果 Y 为各个区间的检验值之和。即:

Yi=j1jvjj[Li,Ri]WjWj

若这批矿产的检验结果与所给标准值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 值对应的答案。

一句话题解:二分答案,每次统计区间符合条件的前缀和,根据答案与标准值的差值更新答案。

下面附上代码:

  1. #include<cstdio>  
  2. #include<algorithm>  
  3. using namespace std;  
  4. typedef long long LL;  
  5. const int MX=200005;  
  6.   
  7. struct Stone{  
  8.     int w,v;  
  9. }stone[MX],pref[MX];  
  10. int n,m,lf=1e9,rt,mid,ml[MX],mr[MX],cnt[MX];  
  11. LL S,diff=1e18;  
  12.   
  13. bool check(int x){  
  14.     int tot=0;  
  15.     LL totv=0,ans=0;  
  16.     for (int i=1;i<=n;i++){  
  17.         pref[i].w=pref[i-1].w+(stone[i].w>=x);  
  18.         pref[i].v=pref[i-1].v+(stone[i].w>=x ? stone[i].v : 0);  
  19.     }  
  20.     for (int i=1;i<=m;i++){  
  21.         ans+=1LL*(pref[mr[i]].w-pref[ml[i]-1].w)*(pref[mr[i]].v-pref[ml[i]-1].v);  
  22.     }  
  23.       
  24.     if (abs(ans-S)<diff)  
  25.         diff=abs(ans-S);  
  26.     return ans>S ? 1 : 0;  
  27. }  
  28.   
  29. int main(){  
  30.     scanf(”%d%d%I64d”,&n,&m,&S);  
  31.     for (int i=1;i<=n;i++){  
  32.         scanf(”%d%d”,&stone[i].w,&stone[i].v);  
  33.         if (stone[i].w<lf) lf=stone[i].w;  
  34.         if (stone[i].w>rt) rt=stone[i].w;  
  35.     }  
  36.     for (int i=1;i<=m;i++){  
  37.         scanf(”%d%d”,ml+i,mr+i);  
  38.     }  
  39.     while (lf<rt){  
  40.         mid=(lf+rt)>>1;  
  41.         if (check(mid)){  
  42.             lf=mid+1;  
  43.         } else {  
  44.             rt=mid;  
  45.         }  
  46.     }  
  47.     check(mid+1);  
  48.     printf(”%I64d”,diff);  
  49.     return 0;  
  50. }  
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值