反悔贪心
反悔贪心=贪心选择+堆调整
只可意会不可言传
更好的理解还是多做题,这里直接把各大佬整理的反悔贪心做一遍吧。
灵神贪心题单中的反悔贪心部分
给定一数组,计算每个前缀和,要求每个结果都为非负数,过程中可以选择将任意数据移到数组末尾,问至少需要移动几次。
如果不管移动多少次都无法满足要求 返回-1
贪心的一直向后累加,如果前缀和出现负值,则此时必须要移动一个数据到末尾。
移动当前数据能否恢复为非负数?显然可以,由于是当前数据累加后导致出现的负值,所以移除必然可行。
是否是最优操作?明显不是,应当移除在此之前出现过的最小负数。
比如数组[100,100,100,-200,-120,-120,200],当计算到-120时 前缀和为-20,此时应当放到末尾的是-200
否则如果移除-120,下一个碰到-120时发现还需要移除。
需要一个结构满足可以快速插入,并且获取所有插入数据的最值。显然这是堆
什么情况下不管移动多少次都无法满足要求?很明显最后一项的前缀和,也就是数组和本身必须是非负数。
如果数组和是非负数是否一定有解?显然是的,因为一定可以把所有正数放在前面,负数放在后面,保证过程中不会出现负数。
//核心代码模式
class Solution {
public:
int magicTower(vector<int>& nums) {
long sum = 0;
for(auto v : nums) sum += v;
if(sum < 0) return -1;
priority_queue<int, vector<int>, greater<int>> pq;
int ans = 0;
long hp = 0;
for(auto v : nums){
hp += v;
if(v < 0) pq.push(v);
if(hp < 0){
hp -= pq.top(); pq.pop();
ans++;
}
}
return ans;
}
};
给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。
你从建筑物 0 开始旅程,如果下个建筑高于当前建筑 则需要使用砖块或者梯子,其中梯子可以确保通过,而砖块需要填充高度差的数量。
计算最远到达的下标。
核心思路是在移动过程中,高度差较大的位置必须使用梯子,较小的位置使用砖块填充。
一种维护方式是,使用小根堆维护前ladders个高度差,如果超过ladders,则多余的部分用砖块填充。
另一种维护方式,使用大根堆,始终用砖块填充,一旦发现无法满足,则pop最大的高度差 改为梯子。
这里有 n 门不同的在线课程,按从 1 到 n 编号。
给你一个数组 courses ,其中 courses[i] = [durationi, lastDayi]
表示第 i 门课将会 持续 上 durationi 天课,并且必须在不晚于 lastDayi 的时候完成。
你的学期从第 1 天开始。且不能同时修读两门及两门以上的课程。
返回你最多可以修读的课程数目。
按最晚时间排序 务必充分理解题目的设定
class Solution {
public:
int scheduleCourse(vector<vector<int>>& courses) {
priority_queue<int> pq;
sort(courses.begin(), courses.end(), [](const vector<int>& A, const vector<int>& B)->bool{return A[1] < B[1];});
int sum = 0;
for(auto v : courses){
pq.push(v[0]);
sum += v[0];
if(sum > v[1]) sum -= pq.top(), pq.pop();
}
return pq.size();
}
};
- 虚拟终点
- auto&v 遍历写法
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
stations.push_back({target, 0});
priority_queue<int> pq;
int pre = 0;
int ans = 0;
for(auto& v : stations){
startFuel -= v[0] - pre;
while(!pq.empty() && startFuel < 0){
startFuel += pq.top(); pq.pop();
ans++;
}
if(startFuel < 0) return -1;
pre = v[0];
pq.push(v[1]);
}
return ans;
}
};
给你一个长度为 n 的整数数组 nums 和一个二维数组 queries ,其中 queries[i] = [li, ri] 。
每一个 queries[i] 表示对于 nums 的以下操作:
将 nums 中下标在范围[li, ri] 之间的每一个元素最多减少1
坐标范围内每一个元素减少的值相互独立
零数组 指的是一个数组里所有元素都等于 0 。
请你返回最多可以从 queries 中删除多少个元素,使得 queries 中剩下的元素仍然能将 nums 变为一个零数组。如果无法将 nums 变为一个零数组,返回 -1 。
枚举每个位置,如果不满足条件,则需要选择向右延伸最远的区间。
每个左端点<=当前位置的区间都要入堆
出堆最远区间直到当前位置满足条件,这个表示选择这个区间
差分维护
class Solution {
public:
int maxRemoval(vector<int>& nums, vector<vector<int>>& queries) {
int n = nums.size(), m = queries.size();
sort(queries.begin(), queries.end(), [](const vector<int>& A, const vector<int>& B)->bool{return A[0] < B[0];});
vector<int> diff(n + 1);
int sum = 0;
priority_queue<int> pq;
for(int i = 0, j = 0; i < n; i++){
sum += diff[i];
while(j < m && queries[j][0] <= i){
pq.push(queries[j][1]);
j++;
}
while(sum < nums[i] && !pq.empty() && pq.top() >= i){
sum++;
diff[pq.top() + 1]--;
pq.pop();
}
if(sum < nums[i]) return -1;
}
return pq.size();
}
};
给你一个长度为 n 的二维整数数组 items 和一个整数 k 。
items[i] = [profiti, categoryi],其中 profiti 和 categoryi 分别表示第 i 个项目的利润和类别。
现定义 items 的 子序列 的 优雅度 可以用 \(total_profit + distinct_categories^2\) 计算
你的任务是从 items 所有长度为 k 的子序列中,找出 最大优雅度 。
- 只有某个类型个数超过1 才能入堆 作为待替换项
- 按profit排序 后续只有新增类型才可能形成更大的优雅度
using i64 = long long;
class Solution {
public:
long long findMaximumElegance(vector<vector<int>>& items, int k) {
int n = items.size();
sort(items.begin(), items.end(), [](vector<int>& A, vector<int>& B)->bool{return A[0] > B[0];});
map<int, int> count;
i64 ans = 0, mx = 0, cc = 0;
priority_queue<vector<int>, vector<vector<int>>, decltype([](vector<int>& A, vector<int>& B)->bool{return A[0] > B[0];})> pq;
for(int i = 0; i < k; i++){
auto& v = items[i];
mx += v[0];
if(!count.contains(v[1])) cc++;
count[v[1]]++;
if(count[v[1]] > 1) pq.push(v);
}
mx += cc * cc;
ans = mx;
for(int i = k; i < n; i++){
auto& v = items[i];
if(pq.empty()) break;
if(count.contains(v[1])) continue;
mx += cc * 2 + 1; //(c + 1)^2 - c^2
mx += v[0] - pq.top()[0]; //cur - old
cc++;
pq.pop();
count[v[1]]++;
ans = max(ans, mx);
}
return ans;
}
};
- 首先是二分
- 然后注意只有某个下标首次出现的位置是有效位置
- 倒序遍历
- DP
应该说是DP好题 - 贪心
这到底怎么贪? - 最小费用最大流
//C#代码+模板
public class Solution {
public long MinimumTotalDistance(IList<int> robot, int[][] factory) {
int n = robot.Count, m = factory.Length;
MCMF_Dinic g = new MCMF_Dinic(n + m + 2);
int s = 0, t = n + m + 1;
for (int i = 1; i <= n; i++) g.AddEdge(s, i, 1, 0);
for (int i = 1; i <= m; i++) g.AddEdge(i + n, t, factory[i - 1][1], 0);
for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) g.AddEdge(i + 1, j + n + 1, 1, Math.Abs(factory[j][0] - robot[i]));
return (g.Dinic(s, t).Item2);
}
}
2599. 使前缀和数组非负
会员题 但是同 LCP 30. 魔塔游戏 一模一样
蒟蒻lndjy的反悔贪心题单
简单题都是相似的 排序 堆维护
P2949 [USACO09OPEN] Work Scheduling G
N项工作 每项包含截止日期和收益,每天只能完成一项,问最大收益
小跟堆,保持前缀天数内堆大小<=天数。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
int n; cin >> n;
vector<vector<i64>> vv;
for (int i = 0; i < n; i++) {
int d, p; cin >> d >> p;
vv.push_back({ d, p });
}
sort(vv.begin(), vv.end(), [](vector<i64>& A, vector<i64>& B)->bool {return A[0] < B[0]; });
priority_queue<i64, vector<i64>, greater<i64>> pq;
for (auto& v : vv) {
pq.push(v[1]);
if (pq.size() > v[0]) pq.pop();
}
i64 ans = 0;
while (!pq.empty()) ans += pq.top(), pq.pop();
cout << ans << endl;
}
大根堆,累计已使用天数,超过则pop最大的。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
int n; cin >> n;
vector<vector<i64>> vv;
for (int i = 0; i < n; i++) {
i64 t1, t2; cin >> t1 >> t2;
vv.push_back({ t1, t2 });
}
sort(vv.begin(), vv.end(), [](vector<i64>& A, vector<i64>& B)->bool {return A[1] < B[1]; });
priority_queue<i64> pq;
i64 total = 0;
for (auto& v : vv) {
pq.push(v[0]);
total += v[0];
if (total > v[1]) total -= pq.top(), pq.pop();
}
cout << pq.size() << endl;
}
Olympiad in Programming and Sports
P6821 [PA 2012 Finals] Tanie linie
给定长度为n的数组 求至多k个不相交子段的和的最大值
浙公网安备 33010602011771号