反悔贪心_Elicsyd的博客-CSDN博客
感觉普通贪心是每一个维度都是平等的,没有优先级。而可后悔贪心是存在某个维度是不可变的,不能直接用排序或者堆进行维护,常常需要经过某种处理,通过挖掘出题目中关于不可变维度的特殊性质,使其可以用排序或者堆等数据结构进行贪心。
可后悔贪心常用堆(priority_queue)进行维护。
问题描述:
您可以完美预测某只股票未来 N 天的价格。您想利用这一知识获利,但每天只想交易一股股票。也就是说,每天你要么买入一股,要么卖出一股,要么什么也不做。起初你拥有的股票为零,当你没有股票时,你不能卖出股票。在N天结束时,你希望再次拥有零股,但希望拥有尽可能多的资金。 — 插件 cf better 翻译
思路:这一题跟股票买卖差不多,但是这个是每次都是可以买/卖/不进行操作,而且对于买卖股票没有次数限制。直接贪心:i天买入,j天卖出,要求j天对于i来说是最大的,但是没有限制次数,这样贪心需要维护区间内的,可能需要用dp。
这题为什么会后悔:价格 a b c,a < b
对于贪心来说要卖出,但是卖出后,到c发现a < b < c
,即|c - a|
大于 |b - a|
,这时会发现我们的贪心就错误了,应该在c再卖出。发现b - a + c - b == c - a
,利用差值可以将局部最优转为全局最优。
具体代码思路:用一个小顶堆维护之前最小的价值,当当前价值大于之前最小的价值时可以卖出,pop后再将当前价值push到小顶堆中。之后在将当前价值push到小顶堆中,表示以当前价值为买入点的股票低点。
以1 4 10 20
为例,答案为25,是1 --> 10 4 --> 20 或者 1 --> 20 4 --> 10
这样。
代码:
void solve() {
int n; cin>>n;
vector<int> a(n);
for(auto &t: a) cin>>t;
priority_queue<int, vector<int>, greater<int>> q;
LL ans = 0;
for(auto t: a) {
if(q.size() && q.top() < t) {
ans += t - q.top();
q.pop();
q.push(t);
}
q.push(t);
}
cout<<ans<<endl;
}
问题描述:
小蓝很喜欢吃巧克力,他每天都要吃一块巧克力。
一天小蓝到超市想买一些巧克力。超市的货架上有很多种巧克力,每种巧克力有自己的价格、数量和剩余的保质期天数,小蓝只吃没过保质期的巧克力,请问小蓝最少花多少钱能买到让自己吃 x 天的巧克力。
思路:这题跟上一个类似,对abc按b进行排序,按b从大到小(从大到小如果后悔简单且一定是可以的)开始进行后悔贪心。与上题不同在于这题还有个数量c,上题没有。可以发现,满足条件只需要x个面包,且对于在i天而言,只要面包的保质期大于等于i,无脑选价钱最小的即可。因此可以按天数从结束天到开始天进行模拟,每次找没过保质期的最小价格的面包。
具体代码思路:用arraya b c
,按b从大到小进行排序。用一个自定义小顶堆来维护对于遍历到i满足面包不过期的剩余面包的最小价格。每次找一个最小价格加入答案即可。
代码:
void solve() {
int n,x; cin>>x>>n;
vector<array<int,3>> va(n);
for(auto &t:va) cin>>t[0]>>t[1]>>t[2]; // a b c
sort(all(va), [](array<int,3> pre, array<int,3> suf) {
return pre[1] > suf[1];
});
auto cmp = [](PII pre, PII suf) {
return pre.vf > suf.vf;
};
priority_queue<PII, vector<PII>, decltype(cmp)> maq(cmp);
LL ans = 0;
int pos = 0;
for(int i = x; i >= 1; --i) {
while(pos < n && va[pos][1] >= i) {
maq.push({va[pos][0], va[pos][2]});
pos++;
}
if(maq.size() == 0) {
cout<<-1;
return ;
}
auto tmp = maq.top(); maq.pop();
ans += tmp.vf; tmp.vs--;
if(tmp.vs != 0) {
maq.push({tmp});
}
}
cout<<ans;
}
问题描述:
小刚在玩 JSOI 提供的一个称之为“建筑抢修”的电脑游戏:经过了一场激烈的战斗,T 部落消灭了所有 Z 部落的入侵者。但是 T 部落的基地里已经有 �N 个建筑设施受到了严重的损伤,如果不尽快修复的话,这些建筑设施将会完全毁坏。现在的情况是:T 部落基地里只有一个修理工人,虽然他能瞬间到达任何一个建筑,但是修复每个建筑都需要一定的时间。同时,修理工人修理完一个建筑才能修理下一个建筑,不能同时修理多个建筑。如果某个建筑在一段时间之内没有完全修理完毕,这个建筑就报废了。你的任务是帮小刚合理的制订一个修理顺序,以抢修尽可能多的建筑。
思路:每一个建筑有一个最后截止时间和抢修耗时。对截止时间从小到大进行(原理同上)排序,用一个变量记录当前时间,如果当前时间加上维修时间在截止时间之内则可以,否则在前面可以的建筑中找一个最耗时的,如果那个最好时的大于当前耗时则进行替换,挤出点时间,为后面进行成功增加几率。
具体代码思路:用pair存入维护时间,截止时间。按截止时间进行升序排序。用一个变量lasttm
记录当前进行到的时间,如果lasttm + vf <= vs
表示这个建筑在截止前可以抢修成功则更新lasttm
,并将维护时间vf
放入到大顶堆中;如果不满足要求,表示这个建筑不可能被维修成功在截止时间之前,则在之前维修好的建筑中将一个维修时间较长的跟这个进行替代,lasttm
更新,次数更新的lasttm
一定比之前的未更新的lasttm
要小。
代码:
void solve() {
int n; cin>>n;
vector<PII> vp(n);
for(auto &t: vp) cin>>t.vf>>t.vs;
sort(all(vp), [&](PII pre, PII suf) {
return pre.vs < suf.vs;
});
int cnt = 0;
int lasttm = 0;
priority_queue<int> pq;
for(int i = 0; i < n; ++i) {
if(lasttm + vp[i].vf <= vp[i].vs) {
cnt++;
lasttm += vp[i].vf;
pq.push(vp[i].vf);
} else if(pq.top() > vp[i].vf) {
lasttm -= pq.top(); pq.pop();
lasttm += vp[i].vf; pq.push(vp[i].vf);
}
}
cout<<cnt;
}
问题描述:
链接:https://ac.nowcoder.com/acm/problem/50439
来源:牛客网
在一个游戏中,tokitsukaze需要在n个士兵中选出一些士兵组成一个团去打副本。
第i个士兵的战力为v[i],团的战力是团内所有士兵的战力之和。
但是这些士兵有特殊的要求:如果选了第i个士兵,这个士兵希望团的人数不超过s[i]。(如果不选第i个士兵,就没有这个限制。)
tokitsukaze想知道,团的战力最大为多少。
思路:按特殊要求的不超过人数进行降序排,因为如果要求人数下降了,可以将之前进入的士兵无脑出队,而不用额外考虑其他信息。
具体代码思路:用pair依次存入战力和要求人数,按要求人数进行降序排序。用小顶堆维护满足要求人数限制条件的士兵的战力。对于第i个士兵,进入答案++,之后处理满足当前士兵要求的人数限制,如果有多余的则将战力最低的出队。每次遍历士兵时都取了max(因为可能中间要求得是最大战力之和,但是记录是遍历到的最后一个士兵时的最大战力之和。
代码:
void solve() {
int n; cin>>n;
vector<PII> vp(n);
for(auto &t: vp) cin>>t.vf>>t.vs; // v s1
sort(all(vp), [](PII pre, PII suf) {
return pre.vs > suf.vs;
});
LL ans = 0;
LL ma = -3;
priority_queue<int,vector<int>,greater<int>> pq;
for(auto t: vp) {
ans += t.vf;
pq.push(t.vf);
while(pq.size() > t.vs) {
ans -= pq.top(); pq.pop();
}
ma = max(ma, ans);
}
cout<<ma;
}
可后悔贪心感觉用排序和堆完美的用上了题目中的性质,解决掉了普通贪心的不足。
发现是贪心 --> 常规贪心有点奇怪 --> 考虑可后悔贪心 / 换算法 / 再挣扎挣扎 --> 其他