[NOIP2011] 聪明的质检员 题解

题解:

  我们首先考虑到,对于一个W,因为区间是确定的,算出来的检验值Y,当W减小时,Y是不增的,因为对满足条件的矿石是不降的(wi>=W)。同理当W增大时,Y是不增的。这意味着Y是一个关于W的单调函数,于是我们就可以二分W值。若当前二分值为Wi,算出来的值为Yi,若S-Yi>0,我们就要设法减小Y值以更靠近S,于是我们就将左边界增大,若S-Yi<0,我们就要设法将Y增大,于是我们就需要将右边界缩小。若S-Yi=0,这是最理想的情况了,就可以直接退出二分。

  大体确定了,还剩下一个问题对于一个W我们如何快速的求出Y。考虑到矿石的价值与数量(wi>=W)是满足“区间可减性(我自己造的词)”的,具体就是若设sum[i]表示从1~i中所有满足条件的矿石的价值和,那么区间[L,R]中满足条件的矿石和为sum[R]-sum[L-1],对于矿石出现的数量也是满足这个条件的。于是我们可以先O(n)扫一遍求出价值与数量的前缀和,然后在O(m)扫一遍所有区间将每个区间的Y值加起来就可以了。总的时间复杂度为O((m+n)log(maxwi))。

附上代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200005;

int n,m,l=1,r;
ll S,ans=((ll)1<<61);
ll sum[N],Fre[N];

struct point1{
    int w,v;
}mine[N];

struct point2{
    int L,R;
}Sec[N];

int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x;
}

int main(){
    scanf("%d%d%lld",&n,&m,&S);
    for(int i=1;i<=n;++i){
        mine[i].w=read(),mine[i].v=read();
        r=max(mine[i].w,r);
    }
    for(int i=1;i<=m;++i) Sec[i].L=read(),Sec[i].R=read();
    while(l<=r){
        memset(sum,0,sizeof(sum)),memset(Fre,0,sizeof(Fre));
        ll ret=0;
        int W=(l+r)>>1;
        for(int i=1;i<=n;++i){//求前缀和
            if(mine[i].w>=W) sum[i]=sum[i-1]+mine[i].v,Fre[i]=Fre[i-1]+1;
            else sum[i]=sum[i-1],Fre[i]=Fre[i-1];    
        }
        for(int i=1;i<=m;++i)
            ret+=(sum[Sec[i].R]-sum[Sec[i].L-1])*(Fre[Sec[i].R]-Fre[Sec[i].L-1]);//求Y值
        ll t=S-ret;
        if(t>0){
            ans=min(ans,t);
            r=W-1;
        }else if(t<0){
            ans=min(ans,-t);
            l=W+1;
        }else if(t==0){
            ans=0;
            break;
        }
    }
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Asika3912333/p/11741189.html