P1803 线段覆盖问题
前言
好!也是学贪心必经之路,背包+线段覆盖(或者是任务分配问题)。也算半个模板题了,理解好本题对于学好贪心算法肯定是基础。
题目
凌乱的yyy / 线段覆盖
题目背景
快 noip 了,yyy 很紧张!
题目描述
现在各大 oj 上有 n n n 个比赛,每个比赛的开始、结束的时间点是知道的。
yyy 认为,参加越多的比赛,noip 就能考的越好(假的)。
所以,他想知道他最多能参加几个比赛。
由于 yyy 是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加 2 2 2 个及以上的比赛。
输入格式
第一行是一个整数 n n n,接下来 n n n 行每行是 2 2 2 个整数 a i , b i ( a i < b i ) a_{i},b_{i}\ (a_{i}<b_{i}) ai,bi (ai<bi),表示比赛开始、结束的时间。
输出格式
一个整数最多参加的比赛数目。
样例 #1
样例输入 #1
3
0 2
2 4
1 3
样例输出 #1
2
提示
- 对于 20 % 20\% 20% 的数据, n ≤ 10 n \le 10 n≤10;
- 对于 50 % 50\% 50% 的数据, n ≤ 1 0 3 n \le 10^3 n≤103;
- 对于 70 % 70\% 70% 的数据, n ≤ 1 0 5 n \le 10^{5} n≤105;
- 对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 6 1\le n \le 10^{6} 1≤n≤106, 0 ≤ a i < b i ≤ 1 0 6 0 \le a_{i} < b_{i} \le 10^6 0≤ai<bi≤106。
题目分析
这类任务分配或者说线段覆盖问题首先要搞清楚数量和总量的区别。这里是贪心算法一般都是要求数量尽可能多,那么我们就要摒弃生活中惯用的总量优先原则,那么就比较好理解局部的最优解。
讲得太深奥我举一个例子,比如说本题要求的是数量最多多少。如果存在0-1、9-10
和0-10
两组,我们会优先选择前者,因为在同样的0-10
的时间里,前者有两个大于后者的一个。但是根据我们的普遍思维是求的总量的话,明显是后者能够充分利0-10
的时间。这就是数量和总量的差别。
理解了前面的,你就可以很容易理解为什么我们本题的局部最优解是结束时间最早。因为结束的越早相当于我们的选择越多,我们也更加可以插入越多的比赛,跟开始时间无关(当然首先不能交叉),跟总时间也无关(事实上我们希望时间越短,时间短的极有可能是最优解)。
原理搞清楚,那么本题就简单了。将比赛按照时间顺序排序,写一个循环使得每次都选取符合条件且结束时间最早的比赛直至最后一个比赛或者没有符合条件的比赛。
具体代码见后文(附注释)
注意事项
1.sort快排函数也可以使用在结构体里,上一题不太清楚,也是学习了一下别人的代码,但是需要自己写一个cmp的比较函数。不过并不困难。
2.本题数量级在106级别,使用冒泡或者选择排序时间复杂度将超过1012次方,按照一秒109次计算来算,肯定会超时,所以我们需要选择时间复杂度更小的算法,比如快速排序。
3.需要理解为什么局部最优解是选取结束时间最短的比赛,这是本题贪心算法的关键,具体解析看题目解析。
4.数组要开大一点,不然就像我一样RE了。
代码
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
struct game {
int start,end;
} a[1000007],t;
bool cmp(game x,game y)
{
return x.end<y.end;
}
int main()
{
int n,count=1,flag=1;
cin>>n;
for(int i=0; i<n; i++)
cin>>a[i].start>>a[i].end;
//按照结束时间排序
sort(a,a+n,cmp) ;
int earliest=0;//当前最早结束时间
while(earliest!=n&&flag) { //当是最后一个或者没有符合条件的时候退出
flag=0;
for(int i=earliest; i<n; i++)
if(a[i].start>=a[earliest].end) {
earliest=i; //更新earliest
count++;//计数加1
flag=1;//说明有符合条件的
}
}
cout<<count;
return 0;
}
后话
错误题解
数组小 加上 没有觉察到必须使用非n2的排序 导致超时和运行错误
相关解决方法在注意事项中已详细说明。
额外用例
输入
输入比较长我绑定到开头了,大家可以自行下载测试
输出
343
卑微求三连
如果感觉本文帮到你那就请你给一个免费的赞、收藏和关注吧!