贪心

区间贪心

区间合并

有n个闭区间sec,将有交集的区间合并(端点重合也是有交集),求合并后的区间数。

先将所有区间按照左端点从小到大排序;beg和end表示当前正在合并成的新区间的左右端点,若下一个待合并区间的左端点在end左侧,更新end为end与该区间右端点的较大者,若在右侧,说明当前新区间[beg, end]已合并完成。

int cnt = 0, beg = 0xc0c0c0c0, end = 0xc0c0c0c0;
for (int i = 0; i < n; i++) {
    if (end < sec[i].l) {
        if (beg != 0xc0c0c0c0) cnt++;
        beg = sec[i].l, end = sec[i].r;
    } else end = max(end, sec[i].r);
}
if (beg != 0xc0c0c0c0) cnt++;

// 合并后区间数为cnt

区间互斥

有n个闭区间sec,选择尽量多的区间,使得选出的区间没有交集(包括端点),求区间数。

先将所有区间按照右端点从小到大排序;end表示已经选出的所有区间所达到的最右端,按顺序找到第一个左端点在end右侧的,更新end为这个区间的右端点。

int cnt = 0, end = 0xc0c0c0c0;
for (int i = 0; i < n; i++) {
    if (end < sec[i].l) {
        cnt++;
        end = sec[i].r;
    }
}

// 区间数保存在cnt中

注:本题还有一种常见的说法叫区间选点,有n个闭区间sec,选择尽量少的点,使得每个区间内至少包含一个选出的点,求点数。

区间覆盖

有n个闭区间sec以及一个线段区间[s, t],选择尽量少的区间,将指定线段区间完全覆盖,求区间数。

先将所有区间按照左端点从小到大排序;找到所有左端点在s左侧的区间中右端点最靠右的一个记为end,更新s为end,如果发现找不到在s左侧的区间,说明无法覆盖。

while(i < n) {
    int end = 0xc0c0c0c0;
    while (i < n && sec[i].l <= s) {
        end = max(sec[i].r, end);
        i++;
    }
    if (end <= s)
        break; // 无法覆盖
    else {
        s = end;
        cnt++;
    }
    if (s >= t)
        break; // 覆盖成功
}

区间分组

有n个闭区间sec,将区间分组,使得每组内部的区间两两之间(包括端点)没有交集,求最小组数。

先将所有区间按照左端点从小到大排序;小顶堆heap保存所有组所达到的最右端(那么组数就等于小顶堆中的元素个数)。如果有区间左端点小于所有组最右端的最小值,说明必须开辟一个新的组,则将这个区间的右端点加入小顶堆;如果区间可以加入已有的组中,则加入最右端最小的组(即堆顶的组),将堆顶弹出并将新加入区间的右端点入堆。

for (int i = 0; i < n; i++) {
    if (heap.empty() || sec[i].l <= heap.top())
        heap.push(sec[i].r);
    else {
        heap.pop();
        heap.push(sec[i].r);
    }
}

公式推导

要求的最优值可以用公式表达,通过分析公式得到最优方案。

例如:Huffman问题、进程调度问题、中位数选址问题。

Huffman问题

将若干结点组合成二叉树,要求是所有叶结点带权路径和最短。

解法:每次从所有结点的集合中选最小的两个结点组合,组合后父结点的数值为两个最小结点数值之和,再将父结点加入集合,直到所有结点全部组合完。

进程调度问题

每个进程都有执行时间,假设若干进程同时到达,要求如何调度进程的顺序,使得所有进程的等待时间总和最小。

解法:按照执行时间由小到大对进程排序,然后按顺序调度进程。

中位数选址问题

在一条直线上有若干个点,在直线上再找一个点,使得它到其他所有点的距离之和最短。

解法:将另找的点设定为这些点的坐标中位数点即可,如果是偶数个点,那么找中间两个点之间的任意位置均可。

最长有序子序列

最长有序子序列(这里以上升为例),可以用贪心+二分进行优化,比动态规划解法时间复杂度更低。

A[1] A[2] A[3] ...... A[n]

数组f[l]存储长度为l的最长上升子序列末尾的最小数,数组f[1...l]是递增的(可以二分),在数组f[1...l]二分查找小于A[i]的最大数,并将A[i]填入该位置。

// 初始化:f[i] = 0 (1<=i<=n)

int l = 1;
f[1] = A[1];

for (int i = 2; i <= n; i++) {
    if (A[i] > d[l])
        f[++l] = A[i];
    else
        f[lower_bound(f+1, f+1+l, A[i])-f] = A[i];