【二分】51Nod 1105 第K大的数

题目链接
题目大概是这样,给两个序列,长度都是n:
a[0]-a[n-1]
b[0]-b[n-1]
现在把a和b两个序列中的每个元素分别相乘,生成n*n个数,即:
a[0]*b[0],a[0]*b[1],a[0]*b[2]……
a[1]*b[0],a[1]*b[1],a[1]*b[2]……
……
然后给出一个数字k,要求出这n*n个数中,第k大的数;

思路:二分套二分

首先我们可以先把a和b数组进行排序,
然后思考一个问题:
给出一个数x,要求出在这n^2个数中,有多少个数大于x;
就是我们希望找到一组a[i]*b[j]>x;
假定对于固定的a[i],我们找到了一个b[j],那么对于这i,我们有n-j+1个数是能满足上述这个条件的,那么我们再去遍历a[i],把所有满足上述条件的数加起来,就是在这n^2个数中,一共有多少个数大于x,就是judge(x);
在找到b[j]的过程,是可以使用二分的,因为考虑到,对于一个规定的a[i],在b[j]的遍历过程中,可以发现到a[i]*b[j]的值是单调递增(不减)的,所以可以进行二分;
但是,这个二分是可以优化的,我们这样考虑,对于递增的i,因为a[i]是递增的,所以a[i]*b[j]的值如果主要满足>x,那么随着a[i]递增,取的b[j]也可以越来越小,又因为b[j]也是单调的,所以我们可以如下实现:

bool judge(ll x){
    int j=n-1,sum=0;
    ll temp;
    for(int i=0;i<n;i++){
        while(a[i]*b[j]>x) j--;
        sum=sum+n-j-1;
    }
    return sum<k;
}

这样判断的复杂度是O(n);
如果是直接二分判断的话,复杂度应该是O(nlogn);

在解决完 给出一个数x,要求出在这n^2个数中,有多少个数大于x; 这个问题后,我们只要找到一个x,使得judge(x)==k,这样这个数就是一个第k大的数,但是,还要保证这个数是在n^2个数中的数,所以我们要找到能满足judge(x)==k的最小的x,这个x才是满足条件的答案;
那么现在问题就变成了,要在left=a[0]*b[0],right=a[n-1]*b[n-1],这样的[left,right]范围内,二分得去找到一个满足judge(x)==k的最小化x;
可以如下实现:

int left=a[0]*b[0],right=a[n-1]*b[n-1],mid;
while(left<right){
    mid=(left+right)>>1;
    if(judge(mid)) right=mid;
    else left=mid+1;
}
ans=left;//left==right;

在写二分的范围和边界条件时,首先要记住,自己选取的开区间还是闭区间,比如我想选取的是闭区间,左闭右闭区间,[left,right],所以我要保证我所取的所有的left或者right都要是能满足答案的解,所以在检验到judge(mid)==false的时候,要left=mid+1;而在judge(mid)==true的时候,可以直接right=mid;在判断循环结束条件时,我常用的判断条件是left < right,这样出循环的时候能满足left=right=ans;

要特别注意二分的范围和边界条件!

最后还要注意,因为在取mid=(left+right)>>1;的时候,mid的取值是偏向左边的(因为是向下取整);
所以如果在循环内的判断中出现了left=mid这样的赋值,就需要把mid改成(left+right+1)>>1,因为避免出现当left+1=right时,会使得mid==left始终成立,进入死循环;

最后完整代码如下:

#include <cstdio>
#include <iostream>
using namespace std;

typedef long long ll;
const int maxx=5e4+6;
ll a[maxx],b[maxx];
int n,k;

bool judge(ll x){
    int j=n-1,sum=0;
    ll temp;
    for(int i=0;i<n;i++){
        while(a[i]*b[j]>x) j--;
        sum=sum+n-j-1;
    }
    return sum<k;
}

int main() {
    std::ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>a[i]>>b[i];
    }
    sort(a, a+n);
    sort(b, b+n);
    ll left=a[0]*b[0],right=a[n-1]*b[n-1],mid;
    while(left<right){
        mid=(left+right)>>1;
        if(judge(mid)) right=mid;
        else left=mid+1;
    }
    cout<<left<<endl;
    return 0;
}

错点:
1.注意二分的边界条件和循环判断条件;
2.注意所以数据都要开longlong,因为a[i]和b[i]的值相乘会超过int的范围;

猜你喜欢

转载自blog.csdn.net/qq_33982232/article/details/81431344
今日推荐