您的位置:首页 > 其它

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

2016-10-27 11:38 369 查看
测试地址:聪明的质监员

做法:可以发现,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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: