题干:
会场安排问题
时间限制:3000 ms | 内存限制:65535 KB
难度:4
描述
学校的小礼堂每天都会有许多活动,有时间这些活动的计划时间会发生冲突,需要选择出一些活动进行举办。小刘的工作就是安排学校小礼堂的活动,每个时间最多安排一个活动。现在小刘有一些活动计划的时间表,他想尽可能的安排更多的活动,请问他该如何安排。
输入
第一行是一个整型数m(m<100)表示共有m组测试数据。
每组测试数据的第一行是一个整数n(1<n<10000)表示该测试数据共有n个活动。
随后的n行,每行有两个正整数Bi,Ei(0<=Bi,Ei<10000),分别表示第i个活动的起始与结束时间(Bi<=Ei)
输出
对于每一组输入,输出最多能够安排的活动数量。
每组的输出占一行
样例输入
2
2
1 10
10 11
3
1 10
10 11
11 20
样例输出
1
2
提示注意:如果上一个活动在t时间结束,下一个活动最早应该在t+1时间开始
解题报告:
以下给出三种方法,,只有一种是正确的 。
1.按照start从小到大排同时end从小到大,然后0~n-1遍历。
2.按照end从大到小排同时start从大到小排,然后0~n-1遍历。
3.按照end从小到大排同时start从小到大排(其实start无所谓),然后0~n-1遍历。
不难看出,只有3是成立的。
证明:首先1和2肯定是绑定的,即如果成立均成立,如果不立均不立。因为其实模拟一组样例画出图来之后倒着看,1和2的方法是一模一样的,或者说,在1中找到一组样例否定掉这种方法,那么样例倒过来之后也可以否定掉第二种方法。
所以下证方法1是错误的:
证明是错误的方法很简单,找一个特例否定掉他:(下面给的是排完序后的)
1,5
6,100
7,9
11,13
15,16
显然。
下证方法3是正确的:
************************************************************************************
经典的区间贪心问题。将每个区间按右端点进行排序,每次第一个区间一定要选,然后重新确定起点,再第一个一定要选。
证明如下:设前两个区间为(a1, b1), (a2, b2),且b1<b2(即已经按b排好序了)
(1)当b1<a2
这种情况区间1和区间2不冲突,这样做一定是对的
(2)当第一个区间与其他区间起冲突了,而且第一个区间可能与很多区间同时起冲突了。但是所以这些起冲突的区间只能取一个。对于第一个区间要么取要么不取两个情况
1)如果最后答案里有这个区间,先取后取一个样的,那么还不如第一下就取了
2)假设最后答案里没有这个区间,我们能推出矛盾,那不就说明“最后答案里没有这个区间“这个假设是错的。因为刚开始起冲突的那些区间一定只能选一个,如果你选了其他,可是这个区间一定可以被第一个替换掉,所以最后答案里又会出现第一个区间。
所以是正确的。
*************************************************************************************
下面给出三种方法的代码:
方法1:
using namespace std;
struct Node {
int s,e;
} node[10000 + 5];
//贪心排序起点做不出来!需要排end!
bool cmp(const Node & a, const Node & b) {
if(a.s!=b.s) return a.s<b.s;
else return a.e<b.e;
}
int main()
{
int m,n;
scanf("%d",&m);
while(m--) {
scanf("%d",&n);
for(int i = 0; i<n; i++) {
scanf("%d %d",&node[i].s,&node[i].e);
}
sort(node,node+n,cmp);
int curs=node[0].s;
int cure=node[0].e;
int ans=1;
int i = 1;
while(i<n) {
if(node[i].s==node[i-1].s) {
curs=node[i].s;
cure=min(cure,node[i].e);
continue;
}
if(node[i].s>cure) {
ans++;
cur
}
}
}
return 0 ;
}
没有写完,,,因为写不动了,,,即 很早的发现了错误。。
方法2:
using namespace std;
struct Node {
int s,e;
} node[10000 + 5];
//贪心排序起点做不出来!需要排end! 这样做从大到小排 依然是错的,因为和排start一样了啊。。你倒着看看 ,画个图
bool cmp(const Node & a, const Node & b) {
if(a.e!=b.e) return a.e>b.e;
else return a.s>b.s;
}
int main()
{
int m,n;
// freopen("in.txt","r",stdin);
scanf("%d",&m);
while(m--) {
scanf("%d",&n);
for(int i = 0; i<n; i++) {
scanf("%d %d",&node[i].s,&node[i].e);
}
sort(node,node+n,cmp);
// for(int i = 0; i<n; i++) {
// printf("%d %d\n",node[i].s,node[i].e);
// }
int curs=node[0].s;
int cure=node[0].e;
int ans=1;
int i = 1;
while(i<n) {
if(node[i].e==node[i-1].e) {
cure=node[i].e;
curs=max(curs,node[i].s);
i++;
continue;
}
if(node[i].e<curs) {
ans++;
cure=node[i].e;
curs=node[i].s;
i++;
}
else {
i++;
}
}
printf("%d\n",ans);
}
return 0 ;
}
这种方法仔细分析会发现就是第一种方法。。
方法3:(ac代码)
using namespace std;
struct Node {
int s,e;
} node[10000 + 5];
//贪心排序起点做不出来!需要排end! 并且是小到大排 !!
bool cmp(const Node & a, const Node & b) {
if(a.e!=b.e) return a.e<b.e;
else return a.s>b.s;//其实这句就没用了
}
int main()
{
int m,n;
// freopen("in.txt","r",stdin);
scanf("%d",&m);
while(m--) {
scanf("%d",&n);
for(int i = 0; i<n; i++) {
scanf("%d %d",&node[i].s,&node[i].e);
}
//养成输入完接着就排序的好习惯,若是放在while前面,,对于本题那就错了!!
sort(node,node+n,cmp);
int curs=node[0].s;//其实curs全程没用,可以删掉。
int cure=node[0].e;
int ans=1;
int i = 1;
while(i<n) {
//如果是 if(node[i].e==node[i-1].e)就错了、、、 //并且这个if可以删掉、、
if(node[i].e==cure) {
cure=node[i].e;
curs=max(curs,node[i].s);
i++;
continue;
}
if(node[i].s>cure) {
ans++;
cure=node[i].e;//思想的精华!!
curs=node[i].s;//curs依旧是,,没啥用、、、
i++;
}
else {
i++;
}
}
printf("%d\n",ans);
}
return 0 ;
}
总结
1.上面这一步就看出 升序排end并且从0递归到n-1的妙处了!!
2.做题方法:先读题并确定需要找出的是什么(即以什么变量为中心点)!
此题根据题意你需要找出的是,每一步的end都是最小的那个(最优解),所以对end排序。
因为你每确定出一个ans,都能保证此时的end是最小的!而start是否满足,不用排序出来,而是if判断一步就好了啊。所以这题关键不是start而是end!(最后一段的理解可以看一下上面我给的样例)
3.对于sort排序,一定要找清楚位置,不然很容易犯很低级的错误并且很难查出来
4.if判断啊逻辑关系要找清楚,第一个if那里,就一直写错了,
写成了if(node[i].e==node[i-1].e)。所以这个if还不如不要,,,反正不影响程序、
5.送上 区间完全不覆盖 模板。
核心语句:
/* for(i=0; i<n; i++){
if(node[i].s>cure){
ans++;
cure=node[i].e;
}
}
*/