51nod 1105 第K大的数

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 123
2 、 3 、 4 2、3、4 234
我们枚举第一个序列,先枚举到 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值