【NOIP2011提高组T5】聪明的质监员-二分答案+前缀和

测试地址:聪明的质监员

做法:可以发现,Y的值是随着W的递增而递减的,满足单调性,因此可以用二分答案把查找答案的复杂度降为O(log n)。设lf为左边界,rt为右边界,lf初始化为0,rt初始化为最大的w[i]+1(即矿石中一个都不满足w[i]≥W的情况),然后进行二分答案。设mid=(lf+rt)/2,对于每个mid,算出Y的值,如果Y<S,则表示左半区间内的数更靠近S,否则表示右半区间内的数更靠近S(想一想,为什么?),一次次地缩小查找范围,就可以找到最好的W了。

那么现在我们只需解决求Y值的问题,由于区间有m个,如果求每个区间的Yi时都要O(n)的复杂度的话,那么整个求Y值的过程的复杂度就是O(nm),显然是不可能承受的。再分析Yi的特征,发现其由两部分组成:w[i]≥W的矿石数量和它们的价值之和。这两个部分都是一个求和的关系,又考虑到在W相同的情况下,这个区间内数的值是不会变化的,所以我们用两个数组sum1和sum2分别表示区间[1,i]内的w[i]≥W的矿石数量和它们的价值之和,那么如果我们要求一个区间[l,r]的Yi,则:

Yi=(sum1[r]-sum1[l-1])*(sum2[r]-sum2[l-1])

这样一来,求Yi的复杂度就降为O(1)了,而sum1和sum2只需计算一次,复杂度为O(n),因而整个求Y值的过程的时间复杂度就是O(n+m),在加上二分答案,整个程序的复杂度就是O((n+m)log n),问题完美解决。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,m,S,w[200010],v[200010],lf=0,rt=0,ans;
ll sum1[200010]={0},sum2[200010]={0};
int l[200010],r[200010];

ll ab(ll a)
{
  if (a<0) return -a;
  else return a;
}

bool calc(ll W)
{
  sum1[0]=sum2[0]=0;
  for(int i=1;i<=n;i++)
  {
    sum1[i]=sum1[i-1];sum2[i]=sum2[i-1];
    if (w[i]>=W) sum1[i]++,sum2[i]+=v[i];
  }
  ll s=0;
  for(int i=1;i<=m;i++)
    s+=(sum1[r[i]]-sum1[l[i]-1])*(sum2[r[i]]-sum2[l[i]-1]);
  if (ab(s-S)<ans) ans=ab(s-S);
  return s<S;
}

int main()
{
  scanf("%lld%lld%lld",&n,&m,&S);
  for(int i=1;i<=n;i++)
  {
    scanf("%lld%lld",&w[i],&v[i]);
	rt=max(rt,w[i]);
  }
  rt++;
  for(int i=1;i<=m;i++)
    scanf("%d%d",&l[i],&r[i]);
  
  ans=1000000;
  ans=ans*ans;
  
  while(lf<=rt)
  {
    ll mid=(lf+rt)>>1;
	if (calc(mid)) rt=mid-1;
	else lf=mid+1;
  }
  
  printf("%lld",ans);
  
  return 0;
}


posted @ 2016-10-27 11:39  Maxwei_wzj  阅读(72)  评论(0编辑  收藏  举报