五月集训(第21天)—堆

1. 2099. 找到和最大的长度为 K 的子序列

    思路:
        将每个元素的值和下标打包,利用堆进行排序,取出其中最大的K个元素,再按照下标恢复原来的顺序即可。详细实现思路见代码注释。

class Solution {
    class Pair {
        public:
        int val;
        int idx;

        Pair() {}
        Pair(int v, int i) : val(v), idx(i) {}
        
        // 重载运算符
        bool operator < (const Pair& o) const {
            return val < o.val;
        }
    };

public:
    vector<int> maxSubsequence(vector<int>& nums, int k) {
        priority_queue<Pair> q;
        int n = nums.size();
        vector<Pair> t;
        // 利用堆排序,为了恢复原顺序,带着下标排序
        for (int i = 0; i < n; i++) {
            q.push( Pair(nums[i], i) );
        }
        // 将最大的 k 个值保留作为答案
        while (k--) {
            t.push_back( q.top() );
            q.pop();
        }
        // 恢复答案的下标顺序
        sort(t.begin(), t.end(), [&](const Pair& a, const Pair& b) {
            return a.idx < b.idx;
        });
        // 转为答案要求的格式
        vector<int> ret;
        k = t.size();
        for (int i = 0; i < k; i++) ret.push_back(t[i].val);
        return ret;
    }
};

2. 1792. 最大平均通过率

    思路:
        主要是要想明白把extraStudents安排到那个班级,要使平均通过率更大,每次安排都要让有更大进步空间的班级提升通过率,这样可以使通过率提高的更多。$(x + 1) / (y + 1) - x / y$ 作为判断依据

class Solution {
    struct klass {
        int up;
        int dn;

        klass(int u, int d) : up(u), dn(d) {}

        // 重载运算的依据是,差分值最大,说明可以提升的通过率更大
        double cp() const{
            return (up + 1.0) / (dn + 1.0) -  (up + 0.0) / dn;
        }

        bool operator < (const klass& k) const {
            // return up / dn > k.up / k.dn;
            return cp() < k.cp();
        }
    };

public:
    double maxAverageRatio(vector<vector<int>>& classes, int extraStudents) {
        priority_queue<klass> q;
        for (int i = 0; i < classes.size(); i++) {
            q.push( klass(classes[i][0], classes[i][1]) );
        }

        // 找到能使通过率提高更多的一个班级,选择该班级,加入一个可以通过的同学
        while (extraStudents--) {
            klass ktop = q.top();
            q.pop();
            q.push( klass(ktop.up + 1, ktop.dn + 1) );
        }

        double ans = 0;
        while (!q.empty()) {
            ans += q.top().up*1.0 / q.top().dn;
            q.pop();
        }
        ans /= classes.size();
        return ans;
    }
};

3. 1499. 满足不等式的最大值

    思路:
         要求 $y_i + y_j + |x_i - x_j|$ 最大值,由于给定了 $x_i$ 严格递增,即$x_i < x_j$,那么可以将绝对值去掉,得到 $y_i + y_j + x_j - x_i$,调换一下顺序就是 $(y_i - x_i) + (y_j + x_j)$ ,那么就可以固定 $x_j, y_j$ 枚举满足 $x_j - x_i <= k$ 的 $i$ 的坐标,找到最大的 $y_i - x_i$ 即可。实现细节见代码注释。

class Solution {
public:
    int findMaxValueOfEquation(vector<vector<int>>& points, int k) {
        priority_queue<pair<int, int>> max_heap;    // 不写 greater<>,或者less<>默认为大根堆
        int n = points.size();
        int ans = INT_MIN;
        // 先放入第一个元素
        max_heap.push(pair{points[0][1] - points[0][0], points[0][0]});    // pair((yi-xi), xi),堆对于pair类型默认按照 pair.first 排序

        // 将第 j 个元素作为固定元素,枚举前面满足条件元素,(此时满足条件的j之前的元素应该在堆中)
        for (int j = 1; j < n; j++) {
            // 如果堆中点的横坐标不满足条件,则一直弹出,直到堆为空,或者找到满足 xj - xi < k 的元素为止
            while (!max_heap.empty() && points[j][0] - max_heap.top().second > k) {
                max_heap.pop();
            }
            // 找到的第一个满足条件的元素,即堆顶元素就是 yi - xi 最大的元素
            if (!max_heap.empty()) {
                ans = max(ans, max_heap.top().first + points[j][0] + points[j][1]);
            }
            // 将当前元素加入堆中,开始以下一个元素作为 xj 枚举前面满足条件的元素
            max_heap.push(pair{points[j][1] - points[j][0], points[j][0]});
        }
        return ans;
    }
};

4. 2163. 删除元素后和的最小差值

    思路:
        将长为 $3n$ 的区间分为左右两部分,分别求出每一部分区间内 $n$ 个元素的和最值,左边区间维护最小值,右边区间维护最大值,此时可以保证左右差值最小。如果视为将 $3n$ 的区间砍为两半,则落刀的位置在区间 $(n - 1, 2n)$ 之间。左右的最值分别利用最大堆和最小堆维护更方便。

class Solution {
public:
    long long minimumDifference(vector<int>& nums) {
        int n = nums.size() / 3;
        priority_queue<int, vector<int>, less<int>> maxq; // 大顶堆
        priority_queue<int, vector<int>, greater<int>> minq; // 小顶堆
        long long ret = 1e10 + 10;

        // left[0~n]存储了[0, n-1], [0, n], [0, n+1], [0, n+2], ... , [0, 2n-1] 共 n+1 个区间内的最小的 n 个数之和
        long long left[100010];
        memset(left, 0, sizeof(left)); /* 注意初始化数组(我本地正常,交上去错,卡了很久) */
        for (int i = 0; i < n; i++) {
            left[0] += nums[i];
            maxq.push( nums[i] );
        }
        for (int i = n; i < 2*n; i++) {
            left[i - n + 1] = left[i - n] + nums[i];
            maxq.push( nums[i] );
            int maxv = maxq.top();
            maxq.pop();
            left[i - n + 1] -= maxv;
        }
        // for (int i = 0; i <= n; i++) cout << left[i] << endl;
        
        // sec计算[n, 3n-1], [n+1, 3n-1], [n+2, 3n-1], ... , [2n-1, 3n-1]共 n + 1 个区间内的最大的 n 个元素之和,但是右边区间的值不需要记录,每次算出来直接跟新答案即可
        long long sec = 0;
        for (int i = 2*n; i < 3*n; i++) {
            sec += nums[i];
            minq.push( nums[i] );
        }
        ret = left[n] - sec;
        for (int i = 2*n - 1; i >= n; i--) {
            sec += nums[i];
            minq.push( nums[i] );
            int minv = minq.top();
            minq.pop();
            sec -= minv;
            ret = min(ret, left[i - n] - sec);
        }
        return ret;
    }
};

posted @ 2022-05-21 13:10  番茄元  阅读(41)  评论(0)    收藏  举报