贪心思想——拦截导弹系统、装箱问题、活动选择

 一、拦截导弹系统

描述

某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统,但是这种拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,由于该系统还在试用阶段。所以一套系统有可能不能拦截所有的导弹。

输入
导弹依次飞来的高度(雷达给出的高度不大于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. 排序:首先将所有物品的体积进行排序(从大到小)。
  2. 选择:然后依次考虑每个物品,如果当前物品可以放入箱子(即其体积不大于箱子的剩余容量),则将其放入箱子中。
  3. 更新:每次放入一个物品后,更新箱子的剩余容量。

(二)局部优解与全局最优

贪心算法并不总是能得出全局最优解,它只在某些特定问题中有效。那么,为什么在这个特定的装箱问题中,贪心算法能够确保得出最优解呢?原因分析如下

  1. 问题的特性
    (1)物品数量有限:在这个问题中,物品的数量是有限的(最多30个),这限制了可能的选择组合的数量。
    (2)箱子容量和物品体积的整数性:箱子容量和每个物品的体积都是正整数,这意味着我们不能对它们进行分割或组合。
    (3)无后效性:在这个问题中,将某个物品放入箱子或不放入箱子是一个不可逆转的决定。一旦做出选择,就不会影响之前或之后其他物品的选择。

  2. 贪心策略的有效性
    (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

(一)解题思路

  1. 贪心选择:在每一步选择中,都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。在这个问题中,贪心选择是选择结束时间最早的活动,因为它为后续的活动留下了最多的时间。

  2. 排序:首先,将所有活动按结束时间的升序排列。这是贪心策略的一部分,因为它允许我们每次选择结束时间最早的活动,同时仍然有机会选择尽可能多的其他活动。

  3. 选择活动:从排序后的列表中,依次选择活动,只要它的开始时间不早于上一个被选择活动的结束时间。这样做可以确保所有被选择的活动都是互不重叠的。

  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;
}