《算法竞赛从入门到国奖》算法基础:入门篇-贪心算法(下)

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》


🌸Yupureki🌸的简介:


目录

区间问题

[1. 凌乱的yyy / 线段覆盖](#1. 凌乱的yyy / 线段覆盖)

算法原理

实操代码

[2. Radar Installation](#2. Radar Installation)

算法原理

实操代码

[3. Sunscreen G](#3. Sunscreen G)

算法原理

实操代码

[4. Stall Reservations S](#4. Stall Reservations S)

算法原理

实操代码


区间问题

1. 凌乱的yyy / 线段覆盖

题目链接:

P1803 凌乱的yyy / 线段覆盖 - 洛谷

算法原理

贪心思想:

我们先把所有的区间按照左端点从小到大排序

排完序如何选择?

如果两个区间不重合

那么我们选择区间1不会影响后续的区间的抉择,相当于是白送一条

如果两个区间重合,有两种情况

我们排完序后肯定是1的左端点小于2的左端点的,那么两种情况无非是1的右端点大于或者小于2的右端点

假设3都被2和1"包裹着",那么选择1和2都不能选3,似乎都可以

但是假设3的左端点大于1或者2的右端点,我们发现左边选择1也可以选择3,右边选择2也可以选择3

因此我们大胆断定,对于两条区间,我们选择右端点较小的那个是最优解

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool cmp(const pair<int,int>& x,const pair<int,int>& y)
{
    return x.first < y.first;
}

int main()
{
    vector<pair<int, int>> v;
    int n; cin >> n;
    while (n--)
    {
        int l, r; cin >> l >> r;
        v.push_back({ l,r });
    }
    sort(v.begin(),v.end(),cmp);
    int ret = 0;
    int r = v[0].second;
    for (int i = 1; i < v.size(); i++)
    {
        int l = v[i].first;
        int r2 = v[i].second;
        if (r <= l)
        {
            r = r2;
            ret++;
        }
        else if (r2 < r)
        {
            r = r2;
        }
    }
    cout << ret + 1;
    return 0;
}

2. Radar Installation

题目链接:

UVA1193 Radar Installation - 洛谷

算法原理

贪心思想:

由图可知,当一个岛屿的坐标确定时,我们可以计算出,哪段区间可以使雷达覆盖这个岛屿

因此我们可以预处理出一个岛屿映射在x轴上的有效雷达区间

之后将这些区间按照左端点从小到大排序,对于重合的两个区间,选择较小的右端点即可

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;

bool cmp(const pair<double, double>& x, const pair<double, double>& y)
{
    return x.second < y.second;
}

int main()
{
    int num = 1;
    int n, m;
    while (1)
    {
        cin >> n >> m;
        if (n == 0 && m == 0)
            break;
        vector<pair<double, double>> v;
        int check = 0;
        for (int i = 0; i < n; i++)
        {
            int x, y; cin >> x >> y;
            if (y > m)
            {
                check = 1;
                v.push_back({ 0,0 });
                continue;
            }
            double l = x - sqrt(m * m - y * y);
            double r = x + sqrt(m * m - y * y);
            v.push_back({ l,r });
        }
        if (check)
        {
            cout << "Case " << num << ": -1" << endl;
            num++;
            continue;
        }
        sort(v.begin(), v.end(), cmp);
        int ret = 0;
        double r = v[0].second;
        for (int i = 1; i < v.size(); i++)
        {
            if (v[i].first > r)
            {
                ret++;
                r = v[i].second;
            }
        }
        cout << "Case " << num << ": " << ret + 1 << endl;
        num++;
    }
    return 0;
}

3. Sunscreen G

题目链接:

P2887 [USACO07NOV] Sunscreen G - 洛谷

算法原理

我们参考之前的做法,可以发现有多种排序和选择的方法

  1. 左端点从大到小 + 防晒霜从大到小
  2. 左端点从小到大 + 防晒霜从大到小
  3. 左端点从大到小 + 防晒霜从小到大
  4. 左端点从小到大 + 防晒霜从小到大
  5. 右端点从大到小 + 防晒霜从大到小
  6. 右端点从小到大 + 防晒霜从大到小
  7. 右端点从大到小 + 防晒霜从小到大
  8. 右端点从小到大 + 防晒霜从小到大

但实际上很多选法是错误的

综合分析,有两种情况是正确的

  1. 左端点从大到小 防晒霜从大到小 选择较大的防晒霜

  2. 右端点从小到大 防晒霜从小到大 选择较小的防晒霜

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool cmp(const pair<int, int>& x, const pair<int, int>& y)
{
    return x.first > y.first;
}

int main()
{
    int n, m; cin >> n >> m;
    vector<pair<int, int>> C;
    vector<pair<int, int>> L;
    for (int i = 0; i < n; i++)
    {
        int l, r; cin >> l >> r;
        C.push_back({ l,r });
    }
    for (int i = 0; i < m; i++)
    {
        int a, b; cin >> a >> b;
        L.push_back({ a,b });
    }
    sort(C.begin(), C.end(), cmp);
    sort(L.begin(), L.end(), cmp);
    int ret = 0;
    for (int i = 0; i < C.size(); i++)
    {
        int l = C[i].first; int r = C[i].second;
        for (int j = 0; j < L.size(); j++)
        {
            int w = L[j].first;
            int& count = L[j].second;
            if (w > r || count == 0)continue;
            if (w < l)break;
            count--;
            ret++;
            break;
        }
    }
    cout << ret;
    return 0;
}

4. Stall Reservations S

题目链接:

P2859 [USACO06FEB] Stall Reservations S - 洛谷

算法原理

贪心思想:

按照「起始时间」对所有奶牛「从小到大」排序,然后「从前往后」依次安排每一头奶牛,设这头奶牛的产奶的时间区间是[a,b]:

在已经有牛的所有牛棚里,如果「结束时间小于a」,就可以把这头奶牛放在这个牛棚里面;如果

有很多符合要求的,我们应该找一个「结束时间最大」的,这样可以将「结束时间较小」的留在后

面,为后面的区间提供更多的机会。

如果所有已经有牛的牛棚的「结束时间都大于」,那么这头牛只能自己单独开一个牛棚。

代码如何实现?

第一步:预处理

  • 读入所有区间,同时记录原始编号(因为后面要排序)
  • 按开始时间排序,开始时间相同则按结束时间排序

第二步:贪心分配

  • 初始化一个最小堆,存储(结束时间, 牛棚编号)
  • 遍历每个区间(按排序后的顺序):
    • 情况A :堆顶的结束时间 < 当前区间开始时间
      • 说明该牛棚已空闲,可以复用
      • 弹出堆顶,将该牛棚分配给当前区间,更新结束时间后重新入堆
    • 情况B :堆空 或 堆顶结束时间 ≥ 当前区间开始时间
      • 说明没有空闲牛棚,需要新建
      • 分配新牛棚编号,将其结束时间入堆

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <functional>
using namespace std;

struct Cow {
    int start, end, id;
};

bool cmp(const Cow &a, const Cow &b) 
{
    if (a.start == b.start) return a.end < b.end;
    return a.start < b.start;
}

int main() 
{
    int N;
    cin >> N;
    vector<Cow> cows(N);
    for (int i = 0; i < N; ++i) 
    {
        cin >> cows[i].start >> cows[i].end;
        cows[i].id = i;
    }
    sort(cows.begin(), cows.end(), cmp);
    // 最小堆:(结束时间, 牛棚编号)
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    vector<int> ans(N);
    int next_id = 1;
    for (const Cow &cow : cows) 
    {
        if (!pq.empty() && pq.top().first < cow.start) 
        {
            // 复用牛棚
            int stall_id = pq.top().second;
            pq.pop();
            pq.push({cow.end, stall_id});
            ans[cow.id] = stall_id;
        } else 
        {
            // 新建牛棚
            ans[cow.id] = next_id;
            pq.push({cow.end, next_id});
            next_id++;
        }
    }
    int ret = next_id - 1;
    cout << ret << "\n";
    for (int i = 0; i < N; ++i) {
        cout << ans[i] << "\n";
    }

    return 0;
}
相关推荐
am心1 小时前
学习笔记-添加购物车
笔记·学习
项目題供诗2 小时前
C语言基础(六)
c语言·开发语言
zzz海羊2 小时前
【CS336】Transformer|2-BPE算法 -> Tokenizer封装
深度学习·算法·语言模型·transformer
_OP_CHEN2 小时前
【算法基础篇】(四十七)乘法逆元终极宝典:从模除困境到三种解法全解析
c++·算法·蓝桥杯·数论·算法竞赛·乘法逆元·acm/icpc
杭州杭州杭州2 小时前
pta考试
数据结构·c++·算法
是娇娇公主~2 小时前
C++集群聊天服务器(1)—— muduo网络库服务器编程
服务器·网络·c++
YuTaoShao2 小时前
【LeetCode 每日一题】2975. 移除栅栏得到的正方形田地的最大面积
算法·leetcode·职场和发展
来两个炸鸡腿2 小时前
【Datawhale组队学习202601】Base-NLP task02 预训练语言模型
学习·语言模型·自然语言处理
carver w2 小时前
张氏相机标定,不求甚解使用篇
c++·python·数码相机