一道二分的题

对于一个长度为n的序列和一个长度为m的序列,它们之间任意两个数的乘积构成了一个长度为n*m的新序列,n和m的范围为10^5,这m*n的范围是10^10,

问新序列中第k大的数是少,

对于这样一个问题,由于范围太大且数组开不下,所以不能采取常规的排序或者树状数组来做,那么剩下想到的只有二分了。

首先将长度为n的a序列和长度为m的b序列分别排序,如果从小到大排序,那么新序列的范围就是a[0]*b[0]~~a[n-1]*b[m-1],然后在这个范围内二分每次

找寻中间值mid,每找到一次就枚举a数列依次然后二分b数列,找到乘积不大于mid的个数然后与k比较,若小于k,则说明mid小了,继续往右边二分不然就是

mid大了然后往左边二分。就是二分次数,讲的不清楚具体看代码吧。

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <iostream>
 4 using namespace std;
 5 const int M=1e5+10;
 6 typedef long long ll;
 7 ll a[M],b[M];
 8 
 9 int main() {
10     int n,m,i;ll k;
11     while(~scanf("%d%d%I64d",&n,&m,&k)){
12         for(i=0;i<n;i++) scanf("%I64d",&a[i]);
13         for(i=0;i<m;i++) scanf("%I64d",&b[i]);
14         sort(a,a+n);
15         sort(b,b+m);
16         k=(ll)n*m-k+1;
17         ll maxMul=a[n-1]*b[m-1];
18         ll l=a[0]*b[0],r=maxMul,mid,cnt,answer = 0;
19         while (l <= r) {
20             mid = l + r >> 1;
21             cnt = 0;
22             for(int i = 0; i < n; ++i) {
23                 int sl = 0, sr = m - 1, smid, scnt = 0;
24                 for(; sl <= sr;) {
25                     smid = sl + sr >> 1;
26                     if(a[i]*b[smid] > mid) sr = smid - 1;
27                     else scnt = smid + 1, sl = smid + 1;
28                 }
29                 cnt += scnt;
30             }
31             if(cnt < k) {
32                 l = mid + 1;
33             } else {
34                 r = mid - 1;
35                 answer = mid;
36             }
37         }
38         printf("%I64d
", answer);
39     }
40     return 0;
41 }
原文地址:https://www.cnblogs.com/JJCHEHEDA/p/5313335.html