1105 第K大的数
1.0 秒 131,072.0 KB 20 分 3级题
数组A和数组B,里面都有n个整数。
数组C共有n^2个整数,分别是:
A[0] * B[0],A[0] * B[1] … A[0] * B[n-1]
A[1] * B[0],A[1] * B[1] … A[1] * B[n-1]
…
A[n - 1] * B[0],A[n - 1] * B[1] … A[n - 1] * B[n - 1]
是数组A同数组B的组合,求数组C中第K大的数。
例如:
A:1 2 3,B:2 3 4。
A与B组合成的C为
B[0] B[1] B[2]
A[0] 2 4 6
A[1] 3 6 9
A[2] 4 8 12
共9个数。
输入
第1行:2个数N和K,中间用空格分隔。N为数组的长度,K对应第K大的数。(2 <= N <= 50000,1 <= K <= 10^9)
第2 - N + 1行:每行2个数,分别是A[i]和B[i]。(1 <= A[i],B[i] <= 10^9)
输出
输出第K大的数。
输入样例
3 2
1 2
2 3
3 4
输出样例
9
【分析】
一道二分+验证的题目。
由于验证也可以二分,所以作为二分套二分的经典题型。
这题有诸多细节:
①二分时对于等于号是否要加上的把握
②精度问题(卡了半天精度,实际上需要转化一下去掉除号)
第一步,意识到不能暴力…
第二步,注意到排序后带来的一些可能的优化,比如利用单调性,比如二分。于是考虑进行二分。
我们假设(二分)第
K
K
K大的数字是
m
i
d
mid
mid,我们怎么直到这个
m
i
d
mid
mid比真实值大还是比真实值小呢?
我们统计比mid大的数字,定义为
a
n
s
ans
ans,如果
a
n
s
>
=
K
ans>=K
ans>=K,说明前面有至少
K
K
K个比
m
i
d
mid
mid大的,
m
i
d
mid
mid一定偏小了,可以将
m
i
d
mid
mid调大,即,往右区间二分;如果
a
n
s
<
K
ans<K
ans<K,说明前面比
m
i
d
mid
mid大的不足
K
K
K个,则
m
i
d
mid
mid要往小了走,这里有个等于号的问题,等于号应该放在哪个分支?显然,等于
K
K
K时,
m
i
d
mid
mid还是偏小的,所以放在第一个分支了。
第三步,如果得知大于
K
K
K的数字个数?总不能直接暴力吧,这里,我们只需二分这个大于
K
K
K的值,同样设为
m
i
d
mid
mid,接下来将两个序列的其中一个排序,如样例:
1
、
2
、
3
1、2、3
1、2、3
2
、
3
、
4
2、3、4
2、3、4
我们枚举第一个序列,先枚举到
1
1
1,然后令
x
=
m
i
d
/
1
x = mid/1
x=mid/1,这样,我们只需二第二个序列中二分
x
x
x的位置,即可找到具体比
x
x
x大的数字个数;然后枚举
2
2
2,同样令
x
=
m
i
d
/
2
x = mid/2
x=mid/2,然后在序列中二分;然后枚举
3
3
3……这样一来做到
n
l
o
g
n
nlogn
nlogn的复杂度,统计出大于
m
i
d
mid
mid的数字个数。
还有一种方法更好,将两个序列都排序,枚举一个序列,另一个序列开一个变量表示大于
x
x
x的位置,由于具有单调性,这个变量只会往一边移动,可以做到
O
(
n
)
O(n)
O(n)。
两种代码都附上了,相对而言二分套二分的做法更具代表性,虽然速度不如第二种验证,但是颇具练习价值。
第四步,本题有精度问题,就是
l
o
n
g
l
o
n
g
longlong
longlong和
m
i
d
/
a
[
i
]
mid/a[i]
mid/a[i]的问题,对于
l
o
n
g
l
o
n
g
longlong
longlong,自己分析开哪些就可以了,对于除法,通过移项转化成乘法,不要卡精度,卡了半个早上无果…
【AC代码】两个check都附上了
#include<cstdio>
#include<algorithm>
#define maxn 50039
using namespace std;
int N, K, a[maxn], b[maxn];
int check(long long g){ //二分验证
long long ans = 0;
for(int i = 1; i < N+1; i++){
long long l = 0, r = N+1, mid;
while(l+1<r){
mid = (l+r)>>1;
if((long long)a[mid]*b[i]>g)r = mid;
else l = mid;
}
ans += N-l;
}
return ans<K;
}
/*int check(long long g){ //线性验证
long long ans = 0, pos = N;
for(int i = 1; i < N+1; i++){
while((long long)a[pos]*b[i]>g)pos--;
ans += N-pos;
}
return ans<K;
}*/
int main(){
freopen("1.in", "r", stdin);
scanf("%d%d", &N, &K);
for(int i = 1; i < N+1; i++)scanf("%d%d", a+i, b+i);
long long l, r, mid;
l = 0, r = 1e20;
sort(a+1, a+1+N);
sort(b+1, b+1+N);
while(l+1<r){
mid = (l+r)>>1;
if(check(mid))r = mid;
else l = mid;
}
printf("%lld", r);
return 0;
}