一、拦截导弹系统
描述
某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统,但是这种拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,由于该系统还在试用阶段。所以一套系统有可能不能拦截所有的导弹。
输入
导弹依次飞来的高度(雷达给出的高度不大于30000的正整数)。计算要拦截所有导弹最小需要配备多少套这种导弹拦截系统。格式
输入格式:n颗依次飞来的高度(1≤n≤1000)。
输出格式:要拦截所有导弹最小配备的系统数k。
样例
输入样例:389 207 155 300 299 170 158 65
输出样例:2
(一)解题思路
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。
在这个问题中,贪心思想体现在:
- 对于每个导弹,我们都希望找到一个已经存在的拦截系统来拦截它,而且这个系统的最低拦截高度应该尽可能低(但又不低于导弹的高度),以便为后续的导弹留下更多的选择空间。
- 如果找不到这样的系统,我们就创建一个新的系统来拦截当前的导弹。
(二)代码
#include <iostream>
#include <cstring>
#define MAXN 1000
using namespace std;
int a[MAXN + 10]; // 导弹飞来时的高度
int l[MAXN + 10]; // 拦截导弹最低高度
// 贪心选择函数,用于更新拦截系统
void updateInterceptors(int &k, int missileHeight, int l[]) {
int p = 0; // 用于记录满足条件的拦截系统索引
for (int j = 1; j <= k; j++) {
if (l[j] >= missileHeight) {
if (p == 0) {
p = j; // 找到第一个能满足条件的拦截系统
} else if (l[p] > l[j]) {
p = j; // 如果存在多个能满足条件的系统,选择最小高度的系统
}
}
}
if (p == 0) {//当前现有系统都无法拦截
k++; // 增加一个新的拦截系统
l[k] = missileHeight; // 更新新系统的最小高度
} else {
l[p] = missileHeight; // 更新原系统的拦截最小值
}
}
int main() {
memset(a, 0, sizeof(a)); // 初始化数组a
memset(l, 0, sizeof(l)); // 初始化数组l
int n = 1;
// 读取导弹高度数据到数组a中
while (cin >> a[n]) {
n++;
}
int k = 1; // 拦截导弹系统数
l[k] = a[1]; // 初始化第一个拦截系统的最小高度
for (int i = 2; i <= n; i++) {
updateInterceptors(k, a[i], l);
}
cout << k << endl; // 输出所需的拦截系统数
return 0;
}
二、装箱问题
描述
有一个箱子容量为V(正整数,0≤v≤20000),同时有n个物品(0< n ≤30),每个物品有一个体积(正整数)。要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
格式
输入格式:第一行是一个整数V,表示箱子容量。第二行是一个整数n,表示物品数。 接下来n行,每行一个正整数(不超过10000),分别表示这n个物品的各自体积。
输出格式:一个整数,表示箱子剩余空间。
样例
输入样例:
24
6
8
3
12
7
9
7输出样例:0
(一)解题思路
在这个装箱问题中,贪心策略表现为:
- 排序:首先将所有物品的体积进行排序(从大到小)。
- 选择:然后依次考虑每个物品,如果当前物品可以放入箱子(即其体积不大于箱子的剩余容量),则将其放入箱子中。
- 更新:每次放入一个物品后,更新箱子的剩余容量。
(二)局部优解与全局最优
贪心算法并不总是能得出全局最优解,它只在某些特定问题中有效。那么,为什么在这个特定的装箱问题中,贪心算法能够确保得出最优解呢?原因分析如下
-
问题的特性:
(1)物品数量有限:在这个问题中,物品的数量是有限的(最多30个),这限制了可能的选择组合的数量。
(2)箱子容量和物品体积的整数性:箱子容量和每个物品的体积都是正整数,这意味着我们不能对它们进行分割或组合。
(3)无后效性:在这个问题中,将某个物品放入箱子或不放入箱子是一个不可逆转的决定。一旦做出选择,就不会影响之前或之后其他物品的选择。 -
贪心策略的有效性:
(1)排序:通过将所有物品按体积从大到小排序,我们确保了首先尝试放入的是体积最大的物品。
(2)选择:由于我们已经按体积排序,因此每次选择都是在当前剩余容量下能够放入的最大体积物品。
(3)更新:每次放入一个物品后,我们更新剩余容量,确保后续的选择仍然基于当前的最佳情况。
(三)代码
#include <stdio.h>
#include <stdlib.h>
int cmp(const void *p1, const void *p2) {
return (*(int *)p1 - *(int *)p2);
}
int main() {
int V, n;
scanf("%d", &V); // 读取箱子容量
scanf("%d", &n); // 读取物品数量
int *v = (int *)malloc(sizeof(int) * n); // 分配内存存储物品体积
for (int i = 0; i < n; i++) {
scanf("%d", &v[i]); // 读取每个物品的体积
}
qsort(v, n, sizeof(int), cmp); // 对物品体积进行排序
for (int i = n - 1; i >= 0; i--) { // 从大到小遍历排序后的物品体积
if (V == 0) { // 如果箱子已满,则跳出循环
break;
}
if (V >= v[i]) { // 如果当前物品可以放入箱子
V -= v[i]; // 放入箱子,并更新剩余容量
}
}
printf("%d\n", V); // 打印箱子剩余空间
free(v); // 释放分配的内存
return 0;
}
三、活动选择
描述
学校在最近几天有n个活动,这些活动都需要使用学校的大礼堂,在同一时间,礼堂只能被一个活动使用。由于有些活动时间上有冲突,学校办公室人员只好让一些活动放弃使用礼堂而使用其他教室。
现在给出n个活动使用礼堂的起始时间begini和结束时间endi(begini < endi),请你帮助办公室人员安排一些活动来使用礼堂,要求安排的活动尽量多。
格式
输入格式:第一行一个整数n(n≤1000);接下来的n行,每行两个整数,第一个begini,第二个是endi(begini < endi≤32767)。
输出格式:输出最多能安排的活动个数。
样例
输入样例
11
3 5
1 4
12 14
8 12
0 6
8 11
6 10
5 7
3 8
5 9
2 13输出样例
4
(一)解题思路
-
贪心选择:在每一步选择中,都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。在这个问题中,贪心选择是选择结束时间最早的活动,因为它为后续的活动留下了最多的时间。
-
排序:首先,将所有活动按结束时间的升序排列。这是贪心策略的一部分,因为它允许我们每次选择结束时间最早的活动,同时仍然有机会选择尽可能多的其他活动。
-
选择活动:从排序后的列表中,依次选择活动,只要它的开始时间不早于上一个被选择活动的结束时间。这样做可以确保所有被选择的活动都是互不重叠的。
-
输出结果:最后,输出可以安排的活动数量。
(二)代码
#include <stdlib.h>
#include <vector>
#include <stdio.h>
#include <algorithm>
using namespace std;
struct Activity {
int start;
int end;
};
bool compare(Activity a, Activity b) {
return a.end < b.end;
}
int main() {
int n;
int res = 0;
scanf("%d", &n);
vector<Activity> lists;
for (int i = 0; i < n; i++) {
Activity tmp;
scanf("%d %d", &tmp.start, &tmp.end);
lists.push_back(tmp);
}
sort(lists.begin(), lists.end(), compare);
int lastTime = 0;
for (const Activity& act : lists) {
if (act.start >= lastTime) {
res++;
lastTime = act.end;
}
}
printf("%d\n", res);
return 0;
}