【每日一题】ABC155D - Pairs | 二分双指针 | 简单

题目内容

原题链接

给定一个长度为 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×(n1) 种不同的元素对中,第 k k k 小乘积是多少。

数据范围

  • 2 ≤ n ≤ 2 × 1 0 5 2\leq n\leq 2\times 10^5 2n2×105
  • 1 ≤ k ≤ n × ( n − 1 ) 2 1\leq k\leq \frac{n\times (n-1)}{2} 1k2n×(n1)
  • − 1 0 9 ≤ a i ≤ 1 0 9 -10^9\leq a_i\leq 10^9 109ai109

题解

二分第 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;
}

你可能感兴趣的:(算法竞赛,算法)