有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问题、进程调度问题、中位数选址问题。
将若干结点组合成二叉树,要求是所有叶结点带权路径和最短。
解法:每次从所有结点的集合中选最小的两个结点组合,组合后父结点的数值为两个最小结点数值之和,再将父结点加入集合,直到所有结点全部组合完。
每个进程都有执行时间,假设若干进程同时到达,要求如何调度进程的顺序,使得所有进程的等待时间总和最小。
解法:按照执行时间由小到大对进程排序,然后按顺序调度进程。
在一条直线上有若干个点,在直线上再找一个点,使得它到其他所有点的距离之和最短。
解法:将另找的点设定为这些点的坐标中位数点即可,如果是偶数个点,那么找中间两个点之间的任意位置均可。
最长有序子序列(这里以上升为例),可以用贪心+二分进行优化,比动态规划解法时间复杂度更低。
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];