codeforces 贪心+优先队列_贪心算法

55ee39475e12fb56d8406cd7e2d424f0.png

基本概念

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)

基本例题

case 1:会议室安排

问题描述:设有n个会议的集合C={1,2,…,n},其中每个会议都要求使用同一个资源(如会议室)而在同一时间内只能有一个会议使用该资源。每个会议i都有要求使用该资源的起始时间bi和结束时间ei,且bi < ei 。如果选择了会议i使用会议室,则它在半开区间[bi, ei)内占用该资源。如果[bi, ei)[bj , ej)不相交,则称会议i与会议j是相容的。会场安排问题要求在所给的会议集合中选出最大的相容活动子集,也即尽可能地选择更多的会议来使用资源。

求解思路:直观上想,一个会议的结束时间越早,留下给其他会议的时间也就越多,进而可能安排的会议数目也就越多。将各个会议按照结束时间从小到大排序,每次所选择的下一个会议需要满足两个条件:该会议的开始时间大于等于上一个会议的结束时间,且是所满足条件的会议中结束时间最早的那一个。

示例代码

#include <iostream>
#include <algorithm>
using namespace std;
struct Meeting {
    int index;
    int startTime;
    int endTime;
}; 
​
bool cmp(Meeting x, Meeting y) {
    return x.endTime < y.endTime;
}
​
void Print(Meeting x) {
    cout<<x.index<<" "<<x.startTime<<" "<<x.endTime<<endl;
}
​
int main() {
    int n;
    cin>>n;
    Meeting meeting[n];
    for(int i=0; i<n; i++) {
        cin>>meeting[i].index>>meeting[i].startTime>>meeting[i].endTime;
    }
    sort(meeting,meeting+n,cmp);
    cout<<"======会议安排如下========"<<endl; 
    Print(meeting[0]);
    int lastTime = meeting[0].endTime;
    for(int i=1; i<n; i++) {
        if(meeting[i].startTime >= lastTime) {
            Print(meeting[i]);
            lastTime = meeting[i].endTime;
        }
    }
    return 0;
}

case 2:分配畜栏

问题描述:农场有N头牛,每头牛会在一个特定的时间区间[A, B](包括AB)在畜栏里挤奶,且一个畜栏里同时只能有一头牛在挤奶。现在农场主希望知道最少几个畜栏能满足上述要求,并要求给出每头牛被安排的方案。对于多种可行方案,只输出一种即可。

输入:输入的第一行包含一个整数N(1 ≤ N ≤ 50, 000),表示有N牛头;接下来N行每行包含两个数,分别表示这头牛的挤奶时间[Ai, Bi](1 ≤ A≤ B ≤ 1, 000, 000)

输出:输出的第一行包含一个整数,表示最少需要的畜栏数;接下来N行,第i+1行描述了第i头牛所被分配的畜栏编号(从1开始)。

解题思路:每头牛都有一个挤奶开始时间和结束时间,当然要优先处理挤奶开始时间早的奶牛。因此首先将所有奶牛按照挤奶开始时间从小到大进行排序,然后按照顺序处理即可,在处理第i头奶牛的时候,首先先查看已经建成的畜栏中是否有空着的(此处可以采用队列,将畜栏按照结束时间从小到大进行排序,队头的畜栏永远是最先结束的,在判断时,只需要判断第一个即可。畜栏的结束时间也就是该畜栏里奶牛挤奶的结束时间),如果有空着的,直接将该头奶牛放进已建成的畜栏即可,如果没有空着的,那么就需要新建一个畜栏,将奶牛放进去挤奶。

示例代码

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
struct Cow {
    int a,b;    // 挤奶区间的起终点
    int No;     // 编号
    bool operator<(const Cow & c) const {
        return a < c.a;
    } 
} cows[50100];
int pos[50100]; // pos[i]表示编号为i的奶牛去的畜栏编号
struct Stall {
    int end;    // 结束时间
    int No;     // 编号
    // 重载<,规定了畜栏被排序的规则:结束时间最早的位于队列的最前方 
    bool operator<(const Stall & s) const {
        return end > s.end;
    }
    Stall(int e, int n):end(e),No(n) { }
}; 
int main() {
    // 读入数据 
    int n;
    scanf("%d", &n);
    for(int i=0; i<n; i++) {
        scanf("%d%d", &cows[i].a, &cows[i].b);
        cows[i].No = i;
    }
    sort(cows,cows+n);  // 对奶牛按照开始挤奶的时间从小到大排序
    int total = 0;      // 记录总共分配了多少畜栏
    priority_queue<Stall> pq;   // 优先队列,用来存放畜栏,按照结束时间从小到大排序 
    for(int i=0; i<n; i++) {
        if(pq.empty()) {
            total ++;
            pq.push(Stall(cows[i].b, total));
            pos[cows[i].No] = total; 
        } else {
            Stall st = pq.top();
            if(st.end < cows[i].a) {
                pq.pop();
                pos[cows[i].No] = st.No;
                pq.push(Stall(cows[i].b,st.No));
            } else {
                total ++;
                pq.push(Stall{cows[i].b, total});
                pos[cows[i].No] = total;
            }
        }
    } 
    cout<<"供分配了 "<<total <<" 个畜栏"<<endl;
    for(int i=0; i<n; i++) {
        cout<<"奶牛编号: "<<cows[i].No+1<<" 畜栏编号: "<<pos[i]<<endl;
    }
    return 0;
}

case 3:放置雷达

问题描述:x轴是海岸线,x轴上方是海洋。海洋中有n个岛屿,可以看作点。给定每个岛屿的坐标(x,y)x,y都是整数。当一个雷达(可看作点)到岛屿的距离不超过d(整数),则认为该雷达覆盖了该岛屿。雷达只能放置在x轴上。问至少需要多少个雷达才可以覆盖全部岛屿。

求解思路:原问题比较复杂,首先我们将问题进行一个转化:对于每个岛屿,可以计算出,覆盖它的雷达,必须位于x轴上的区间[Ps,Pe]。如下图所示:

6a013c295f4b8d1faf0695f998a9bac5.png

如果有雷达位于某个x轴区间[a,b],称该雷达覆盖此区间。问题转化为,至少要在x轴上放置几个雷达(点),才能覆盖全部区间。

  • 重要结论:如果可以找到一个雷达同时覆盖多个区间,那么把这多个区间按照起点坐标从小到大排序,则最后一个区间(起点最靠右)k的起点,就能覆盖所有区间。因此,我们就可以只挑区间的起点来放置雷达了。
  • 首先将所有区间按照从小到大排序,并编号0~n-1。依次考察每个区间的起点,看要不要在那里放置雷达。开始,所有的区间都没有被覆盖,定义一个变量firstNoConvered指向编号最小的且未被覆盖的区间编号,因此最初始状态firstNoConvered=0。接着我们依次遍历起点,在第0个区间起点可放置的情况下,先不着急放置雷达,再看放置第1个区间起点是否可以覆盖前面的0区间,如果可以继续往下看第2个区间的起点,看放置在该点是否可以覆盖前面的0,1号区间,如果不可以的话,就将第一个雷达放置在1号区间的起点,然后firstNoConvered=2指向第二个区间;如果可以的话继续按照该思路往下看

输入与输出:输入有两部分构成。第一行输入mrm代表海上岛屿的数量,r代表雷达的覆盖半径。接下来的m行分别表示各个雷达的坐标(x,y)。输出放置最小数量的雷达数目即可。

示例代码

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int R = 2;
struct Point {
    int x;
    int y;
};
​
struct QuJian {
    int No;
    int left;
    int right;
    bool operator<(const QuJian & qujian) const {
        return left < qujian.left;
    } 
};
​
int main() {
    // 输入数据 
    int m,r;
    cin>>m>>r;
    Point point[m];
    for(int i=0; i<m; i++) {
        cin>>point[i].x>>point[i].y;
    }
    // 转化为区间 
    QuJian qj[m];
    for(int i=0; i<m; i++) {
        qj[i].left = point[i].x - sqrt(R*R-point[i].y*point[i].y);
        qj[i].right = point[i].x + sqrt(R*R-point[i].y*point[i].y);
    }
    // 区间按照左端点从小到大排序
    sort(qj,qj+m);
    int total = 0;
    for(int i=0; i<m; i++) {
        if(i == m-1) {
            cout<<"最少放置雷达数: "<<total+1<<endl;
            return 0;
        }
        int minRight = qj[i].right;
        for(int j=i+1; j<m; j++) {
            if(qj[j].left <= minRight) {
                minRight = min(minRight,qj[j].right);
                if(j == m-1) {
                    cout<<"最少放置雷达数: "<<total+1<<endl;
                    return 0;
                }
            } else {
                total ++;   // 放置雷达在编号为j-1的区间起点
                i = j;
                i--;        // break之后i还要+1,所以要先-1,才能将j的值赋给i 
                break; 
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值