贪心练习专题

反悔贪心

一般的贪心是先考虑局部最优,再从局部最优求出整体最优。但是可能会遇到一些情况导致局部最优推出来不是整体最优,可能会存在更优的方案。这时候就要加入反悔操作,结合具体题目可能比较容易理解。

Work Scheduling G

这个题目需要我们求出在截至时间内,能获得的最大利润。如果我们按照每项工作的利润从大到小排序,然后依次选择的方法来写,遇到一下这个情况的时候就不是最优解了。


按照刚才的方法,我们选择第一项和第三项工作获得17的利润。但是很明显,要是按照选择二、三、一的顺序来选择,这是合法的,能获得25的利润,比刚才的方法更优。这种情况还看不出来什么,接着看下面的例子。


这个例子的最优解是按照二、三、四选择工作,得到57的利润。按照利润排序的方法行不通,那我们按照截止时间来对工作进行排序。在这个例子中,按照这个方法选择,遇到第三项工作不合法,但是选择第三项工作是比选择第二项工作更优的,这里就要进行反悔操作。要是遇到不合法的情况,就将已经选择的利润最小的工作取消,看看把当前这的工作加入进去。对于这个题来说,只要弹出了一项工作,一定就能加入一项新的工作,因为每个工作花费时间都是1。这个操作就需要用小根堆来实现。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;

const int N = 1e5;

void solve(){
    int n;
    cin >> n;
    vector<PII> a(n);
    for (int i = 0; i < n; i++) cin >> a[i].second >> a[i].first;
    sort(a.begin(), a.end(), [&](PII x, PII y){ //按照截止时间排序
        if (x.second == y.second) return x.first > y.first;
        return x.second < y.second;
    });
    ll sum = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    for (int i = 0; i < n; i++){
        if (a[i].second > q.size()){ // 如果这项工作的截止时间还没到
            sum += a[i].first;
            q.push(a[i]);
        } else{
            if (a[i].first > q.top().first){ //要是利润最低的低于本工作就更换
                sum -= q.top().first;
                q.pop();
                q.push(a[i]);
                sum += a[i].first;
            }
        }
    }
    cout << sum;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while(T--){
        solve();
    }
    return 0;
}

反悔贪心的大概思路就是按照题目的限制条件进行排序,再维护一个大(小)根堆,遇到不合法的选项就将其和堆顶元素比较,要是更优就弹出堆顶,将其放入堆中。注意,比较出来更优的一定要是合法的(即一定能被放入堆中),才能使用反悔贪心。

建筑抢修

这个题本质上和上一题没多大区别,区别在于弹出的比较条件不同。我们先按照限制条件(建筑报废时间)从小到大排序,要是遇到不能修好的建筑,就将这个建筑和已经选择修好的建筑中花费时间最久的比较,将花费时间小的放入堆中。因为我们是按照截止时间排序的,所以弹出一个修理任务之后,我们花费的时间会减小,但是这个建筑的报废时间一定要比弹出的建筑的报废时间更晚,所以一定能在这个建筑报废前修好它。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;

const int N = 1e5;

void solve(){
    int n;
    cin >> n;
    vector<PII> a(n);
    for (int i = 0; i < n; i++) cin >> a[i].first >> a[i].second;
    sort(a.begin(), a.end(), [&](PII x, PII y){
        if (x.second == y.second) return x.first < y.first;
        return x.second < y.second;
    });
    ll time = 0, ans = 0;
    priority_queue<ll> q;
    for (int i = 0; i < n; i++){
        if (time + a[i].first < a[i].second){
            time += a[i].first;
            ans++;
            q.push(a[i].first);
        } else{
            if (a[i].first < q.top()){
                time -= q.top();
                q.pop();
                q.push(a[i].first);
                time += a[i].first;
            }
        }
    }
    cout << ans;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while(T--){
        solve();
    }
    return 0;
}

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值