【题目链接】
【题目考点】
1. 贪心
2. 贪心选择性质的证明
要想证明贪心选择可以得到最优解,只需要证明最优解包含每一次的贪心选择。
使用数学归纳法:
- 证明最优解包含第一次的贪心选择
- 假设存在最优解包含前k次的贪心选择,证明该最优解包含第k+1次的贪心选择
【解题思路】
每次选择右端最小的区间的右端数字,并删掉所有包含该数字的区间。
用数学语言说,现有区间:
e
1
=
[
l
1
,
r
1
]
,
e
2
=
[
l
2
,
r
2
]
,
.
.
.
,
e
n
=
[
l
n
,
r
n
]
e_1=[l_1,r_1], e_2=[l_2,r_2],...,e_n=[l_n,r_n]
e1=[l1,r1],e2=[l2,r2],...,en=[ln,rn]
选择右端点r最小的区间
e
m
=
[
l
m
,
r
m
]
e_m=[l_m,r_m]
em=[lm,rm],删掉所有满足
r
m
∈
e
x
r_m\in e_x
rm∈ex的区间
e
x
e_x
ex
1. 贪心选择性质的证明:
贪心选择:每次选择右端最小的区间的右端点数字
- 证明:存在最优解包含右端最小的区间的右端点数字
记右端最小的区间为 e m = [ l m , r m ] e_m = [l_m,r_m] em=[lm,rm],证明存在最优解包含 r m r_m rm
使用反证法:假设所有最优解都不包含 r m r_m rm
对于某一最优解,根据题意:“对于每一个区间,都至少有一个整数属于该集合”,所以一定要在区间 e m e_m em中选择一个数字。假设该最优解中选择了区间 e m e_m em中的数字 x x x且 x ≠ r m x \neq r_m x=rm,包含x的区间有p个,组成的集合为: E x = { e x 1 , e x 2 , . . . , e x p } E_x = \{e_{x1},e_{x2},...,e_{xp}\} Ex={ex1,ex2,...,exp},选择x后,这些区间都被删掉,不再被考虑。
现在考虑在该最优解中用 r m r_m rm替换 x x x,包含 r m r_m rm的区间有q个,组成集合为: E r = { e r 1 , e r 2 , . . . , e r q } E_r=\{e_{r1},e_{r2},...,e_{rq}\} Er={er1,er2,...,erq},由于 e m e_m em是右端最小的区间,所以 E x E_x Ex中的所有区间的右端都大于等于 r m r_m rm,也就是说 r m r_m rm在 E x E_x Ex的每个区间中,亦即 E x ⊆ E r E_x\subseteq E_r Ex⊆Er。选择 r m r_m rm后, E r E_r Er中的所有区间都被删除,包括了 E x E_x Ex中的所有区间。该最优解中其他数字不变,仍然可以形成一组最优解。而这组最优解包含了 r m r_m rm,与假设相悖。因而原命题得证。
- 证明:在k次贪心选择后,存在最优解包含这一次的贪心选择:剩余的区间中右端最小的区间的右端点数字
证明方法与证明第1点相似,不再赘述。
2. 具体做法
将所有区间排序,排序规则为:右端小的排在前面;如果右端相同,那么左端小的排在前面。
按顺序遍历各个区间,选择当前区间的右端点并记录,选择的数字个数加1。
继续遍历,如果当前区间的左端点小于等于之前记录的右端点,那么略过这个区间。
如果当前区间左端点大于之前记录的右端点,那么选择当前区间的右端点并记录,选择的数字个数加1。
重复上述过程,直至遍历完所有区间。
【题解代码】
解法1:贪心
#include<bits/stdc++.h>
using namespace std;
struct Sec
{
int l, r;//l:区间左端点 r:区间右端点
};
bool cmp(const Sec &a, const Sec &b)
{//右端点更小的排在前面,右端点相同则左端点更小的在前面
return a.r < b.r || a.r == b.r && a.l < b.l;
}
int main()
{
Sec s[10005];
int n, ct = 0, lastR = -1;//lastR:上一个选择的右端点 ct:选择的数字个数
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> s[i].l >> s[i].r;
sort(s+1, s+1+n, cmp);
for(int i = 1; i <= n; ++i) if(s[i].l > lastR)
{
ct++;
lastR = s[i].r;
}
cout << ct;
return 0;
}