【noip2010】聪明的质检员——二分法与前缀和的结合

这道题很多OJ上都有,例如洛谷.

自己找题目吧.

这道题很容易想到枚举W,但是会TLE.

所以很容易想到二分,而可二分性的证明就很简单了.

W越大,Y就越小.

因为所有数都为正整数,所以vj肯定是大于0的.

所以j的个数也是正的加上去.

而W越大说明满足条件的j越少,所以Yi就越小,那么Y也就越小.

那么可二分了.

那么二分的check函数写出来(是个long long),就很容易了.

我们直接暴力,时间复杂度为O(n^2*log(n)).

那么我们分析一下这题的性质,那么我们可以用前缀和来处理,直接有条件的前缀和处理每一块矿石.

那么有条件的前缀和就是每一次check都重求一遍,当这块矿石满足了条件才能加进来,否则不加.

同时也要处理一个num数组,其中num[i]表示矿石1~i中有几个满足要求.

那么前缀和就这么处理:

  ll Y=0;
  A[0]=0;num[0]=0;
  for (ll i=1;i<=n;i++)
    if (a[i].w>=W) A[i]=A[i-1]+a[i].v,num[i]=num[i-1]+1;
    else A[i]=A[i-1],num[i]=num[i-1];

然后暴力枚举每个区间就行了:

  for (ll i=1;i<=m;i++)
    Y+=(num[e[i].r]-num[e[i].l-1])*(A[e[i].r]-A[e[i].l-1]);
  return Y;

那么整个check函数如下:

ll A[M+1];
ll num[M+1];
ll check(ll W){
  ll Y=0;
  A[0]=0;num[0]=0;
  for (ll i=1;i<=n;i++)
    if (a[i].w>=W) A[i]=A[i-1]+a[i].v,num[i]=num[i-1]+1;
    else A[i]=A[i-1],num[i]=num[i-1];
  for (ll i=1;i<=m;i++)
    Y+=(num[e[i].r]-num[e[i].l-1])*(A[e[i].r]-A[e[i].l-1]);
  return Y;
}

那么AC正解就很简单了,套个二分就行了:

#include<bits/stdc++.h>
  using namespace std;
#define rep(i,j,k) for (ll i=j;i<=k;i++)
typedef long long ll;
const ll M=200000;
ll n,m,S;
ll minn=M*M*1000;
struct none{
  ll w,v;
}a[M+1];
struct seg{
  ll l,r;
}e[M+1];
inline void into(){
  scanf("%lld%lld%lld",&n,&m,&S);
  rep(i,1,n)
    scanf("%lld%lld",&a[i].w,&a[i].v);
  rep(i,1,m)
    scanf("%lld%lld",&e[i].l,&e[i].r);
}
ll A[M+1];
ll num[M+1];
ll check(ll W){
  ll Y=0;
  A[0]=0;num[0]=0;
  for (ll i=1;i<=n;i++)
    if (a[i].w>=W) A[i]=A[i-1]+a[i].v,num[i]=num[i-1]+1;
    else A[i]=A[i-1],num[i]=num[i-1];
  for (ll i=1;i<=m;i++)
    Y+=(num[e[i].r]-num[e[i].l-1])*(A[e[i].r]-A[e[i].l-1]);
  return Y;
}
inline void work(){
  ll l=1,r=200000,mid=(l+r)>>1;
  while (l+1<r){
    if (check(mid)<=S) r=mid;
    else l=mid;
    mid=(l+r)>>1;
  }
  minn=min(minn,abs(check(l)-S));
  minn=min(minn,abs(check(r)-S));
}
inline void outo(){
  printf("%lld\n",minn);
}
int main(){
  into();
  work();
  outo();
  return 0;
}

好了.

这题简单.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/80313975