【NOIP2017提高组模拟12.24】B

Description

现在你有N个数,分别为A1,A2,…,AN,现在有M组询问需要你回答。每个询问将会给你一个L和R(L<=R),保证Max{Ai}-Min{Ai}<=R-L,你需要找出并输出最小的K(1<=K<=N,不存在输出-1)满足以下两个条件:
①能够在原来的N个数中选出不重复(下标不重复)的K个数,使得这K个数的和在区间[L,R]内。
②能够在原来的N个数中选出不重复(下标不重复)的K个数,使得这K个数的和不在区间[L,R]内。
N,M<=100000

Input

输入第一行两个正整数N,M。
第二行N个数,表示A1,A2,…,AN
接下来M行表示M组询问,每组询问两个正整数L,R,意义如上。

Output

输出共M行,每一行输出对应询问的最小的K(不存在输出-1)。

Sample Input

5 3
3 6 5 8 7
29 34
2 8
1 10

Sample Output

-1
2
2

Solution

先把原数列排序,因为再选值的时候不需要连续
记录前缀和还有后缀和
对于输入的l和r
分别用二分找到:
x:前缀和小于l的最后一个位置
y:后缀和大于r的第一个位置
z:后缀和大于等于l的第一个位置
q:前缀和小于等于r的最后一个位置
y和z还需要用n减一下

有什么用呢?
设k为取多少个数
对于k=1~x,都可以取到k个数的和小于l,即满足条件2
对于k=n-y+1~n,都可以取到k个数的和大于r,即满足条件2
也就是说当k为1~x或y~n时都可以满足条件2

由题意“保证Max{Ai}-Min{Ai}<=R-L”得,当你现在的q个数的和还小于l时,再加一个数不可能大于r
那么当k=z~n时,都可以取到k个数的和大于l
k=1~n-q+1时,都可以取到k个数的和小于r
也就是k=z~n-q+1时,都可以满足条件2,然后求交集中的最小值就行了

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define fd(i,a,b) for(ll i=a;i>=b;i--)
#define N 101000
#define M 300
#define ll long long
using namespace std;
ll a[N],n,s[N],g[N],q,l,r;
ll find(ll x)
{
    ll l=0,r=n;
    while(l+1<r)
    {
        ll m=(l+r)/2;
        if(s[m]<x) l=m;else r=m;
    }
    return l;
}
ll find1(ll x)
{
    ll l=0,r=n;
    while(l+1<r)
    {
        ll m=(l+r)/2;
        if(g[m]>x) l=m;else r=m;
    }
    return l;
}
ll find2(ll x)
{
    ll l=0,r=n;
    while(l+1<r)
    {
        ll m=(l+r)/2;
        if(s[m]<=x) l=m;else r=m;
    }
    if(s[r]<=x) l=r;
    return l;
}
ll find3(ll x)
{
    ll l=0,r=n;
    while(l+1<r)
    {
        ll m=(l+r)/2;
        if(g[m]>=x) l=m;else r=m;
    }
    if(g[r]>=x) l=r;
    return l;
}
int main()
{
    scanf("%lld%lld",&n,&q);
    fo(i,1,n) scanf("%lld",&a[i]);
    sort(a+1,a+n+1);
    fo(i,1,n) s[i]=s[i-1]+a[i];
    fd(i,n,1) g[i]=g[i+1]+a[i];
    for(;q;q--)
    {
        scanf("%lld%lld",&l,&r);
        ll x=find(l),y=find1(r),z=find3(l),q=find2(r);
        z=n-z+1;y=n-y+1;
        ll ans=-1;
        if(z<=x) ans=z;
        else if(q>=y) ans=y;
        printf("%lld\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值