给定一个长度为 n n n 的整数数组 a a a 。
选择两个不同的下标 i i i 和 j j j ,乘积为 a i × a j a_i\times a_j ai×aj
问在 n × ( n − 1 ) 2 \frac{n\times (n-1)}{2} 2n×(n−1) 种不同的元素对中,第 k k k 小乘积是多少。
二分第 k k k 小乘积 m i d mid mid。
然后 c h e c k check check 是否有至少 k k k 个元素对的乘积小于等于 m i d mid mid 。
在 c h e c k check check 中使用双指针来降低时间复杂度,如果在 c h e c k check check 中也使用二分,就得多一个 O ( log n ) O(\log n) O(logn) 的复杂度。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn)
#include
using namespace std;
typedef long long ll;
int main()
{
int n;
cin >> n;
ll k;
cin >> k;
vector<int> pos, neg;
ll zero = 0;
for (int i = 0; i < n; ++i) {
int x; cin >> x;
if (x == 0) zero += 1;
else if (x > 0) pos.push_back(x);
else neg.push_back(x);
}
int nn = int(neg.size());
int np = int(pos.size());
zero = zero * (zero - 1) / 2 + zero * (nn + np);
sort(pos.begin(), pos.end());
sort(neg.begin(), neg.end(), greater<>());
ll mul_neg = 1ll * nn * np;
if (k <= mul_neg) {
auto check = [&](ll mid) {
// mul 为负数小于等于 mid 的
// 考虑正数对应的所有负数即可,这块可以双指针
// 枚举每个正数,找到对应的一个最大的负数,满足两数之积小于等于 mid
ll cnt = k;
for (int i = np - 1, j = 0; i >= 0; --i) {
while (j < nn && 1ll * pos[i] * neg[j] > mid) j += 1;
cnt -= (nn - j);
}
return cnt <= 0;
};
ll l = -1e18, r = -1;
while (l < r) {
ll mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << "\n";
} else if (k <= mul_neg + zero) {
cout << "0\n";
} else {
auto check = [&](ll mid) {
// mul 为正数小于等于 mid 的
ll cnt = k - mul_neg - zero;
// 正数 * 正数
for (int i = 0, j = np - 1; i < j; ++i) {
while (j >= 0 && 1ll * pos[i] * pos[j] > mid) j -= 1;
if (i < j) cnt -= j - i;
}
if (cnt <= 0) return true;
// 负数 * 负数
for (int i = 0, j = nn - 1; i < nn; ++i) {
while (j >= 0 && 1ll * neg[i] * neg[j] > mid) j -= 1;
if (i < j) cnt -= j - i;
}
return cnt <= 0;
};
ll l = 1, r = 1e18;
while (l < r) {
ll mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << "\n";
}
return 0;
}