题目链接
题目大概是这样,给两个序列,长度都是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的范围;