题目
使 K 个子数组内元素相等的最少操作数
题解
这题就是大杂烩,使用了延迟堆+滑动窗口来求出每个窗口内所有值变成它中位数的操作次数,最后再用划分型dp求出最小操作数。
我们先一个一个来分析,如何求取一块区间内的中位数,我们可以用对顶堆实现,可以参考题目295. 数据流的中位数。
这是不对元素进行删除时中位数的求法,那如果我们要用一个窗口不断往右走,那该如何求这个窗口内的中位数呢?
由于我们每次移动后前面的值出了窗口,我们如果每次遍历堆找出那个值时很麻烦的,这时候我们想到了延迟堆的做法,删除的时候,我们只记录「要删除一个值为 x 的数」,并不去「执行」删除操作。等到要出堆(或者查看堆顶)时才真正地执行删除操作。
那我们如何求的这个窗口内将所有值变成一样的操作数呢?
我们可以参考题目2602. 使数组元素全部相等的最少操作次数,题解用的是排序+前缀和+二分查找,我们可以用面积来求的需要的操作数。
那我们只需要在延迟堆的代码中再开一个变量维护和大小即可。
最后就是如何使用dp完成了。
将数组分成(恰好/至多)k 个连续子数组,计算与这些子数组有关的最优值。一般定义 f[i][j] 表示将长为 j 的前缀 a[:j] 分成 i 个连续子数组所得到的最优解。枚举最后一个子数组的左端点 L,从 f[i−1][L] 转移到 f[i][j],并考虑 a[L:j] 对最优解的影响。
本题f[i][j]表示前 j 个元素(即 nums[0..j-1])分成 i 个长度为 x 的子数组时,最小的操作数。只需要初始化f[i][i*x−1]=∞。并不需要初始化 j<i⋅x−1 的状态,因为我们不会访问这些状态。
参考代码
template<typename T, typename Compare = less<T>>
class LazyHeap {priority_queue<T, vector<T>, Compare> pq;unordered_map<T, int> remove_cnt;size_t sz = 0;long long s = 0;void apply_remove() {while(!pq.empty() && remove_cnt[pq.top()] > 0) {remove_cnt[pq.top()]--;pq.pop();}}public:size_t size(){return sz;}long long sum() {return s;}void remove(T x) {remove_cnt[x]++;sz--;s -= x;}T top() {apply_remove();return pq.top();}T pop() {apply_remove();T x = pq.top();pq.pop();sz--;s -= x;return x;}void push(T x) {if(remove_cnt[x] > 0) remove_cnt[x]--;else pq.push(x);sz++;s += x;}T push_pop(T x) {apply_remove();pq.push(x);s += x;x = pq.top();pq.pop();s -= x;return x;}
};class Solution {vector<long long> medianSlidingWindow(vector<int>& nums, int k) {int n = nums.size();vector<long long> ans(n - k + 1);LazyHeap<int> left;LazyHeap<int, greater<int>> right; for (int i = 0; i < n; i++) {int in = nums[i];if (left.size() == right.size()) {left.push(right.push_pop(in));} else {right.push(left.push_pop(in));}int l = i + 1 - k;if (l < 0) continue;long long v = left.top();long long s1 = v * left.size() - left.sum();long long s2 = right.sum() - v * right.size();ans[l] = s1 + s2;int out = nums[l];if (out <= left.top()) {left.remove(out);if (left.size() < right.size()) {left.push(right.pop()); }} else {right.remove(out);if (left.size() > right.size() + 1) {right.push(left.pop()); }}}return ans;}
public:long long minOperations(vector<int>& nums, int x, int k) {int n = nums.size();vector<long long> dis = medianSlidingWindow(nums, x);vector f(k + 1, vector<long long>(n + 1));for(int i = 1;i <= k; i++) {f[i][i * x - 1] = LLONG_MAX;for(int j = i * x; j <= n - (k - i) * x; j++) {f[i][j] = min(f[i][j - 1], f[i - 1][j - x] + dis[j - x]);}}return f[k][n];}
};