一、题目描述
标题: 1.2-1挤牛奶
标签: 模拟 排序
详情:
三个农民每天清晨5点起床,然后去牛棚给3头牛挤奶。第一个农民在300秒(从5点开始计时)给他的牛挤奶,一直到1000秒。第二个农民在700秒开始,在 1200秒结束。第三个农民在1500秒开始2100秒结束。期间最长的至少有一个农民在挤奶的连续时间为900秒(从300秒到1200秒),而最长的无人挤奶的连续时间(从挤奶开始一直到挤奶结束)为300秒(从1200秒到1500秒)。
你的任务是编一个程序,读入一个有N个农民(1 <= N <= 5000)挤N头牛的工作时间列表,计算以下两点(均以秒为单位):
最长至少有一人在挤奶的时间段。
最长的无人挤奶的时间段。(从有人挤奶开始算起)
输入格式:
第1行:一个整数N。
接下来N行:每行两个小于1000000的非负整数,表示一个农民的开始时刻与结束时刻。
输出格式:
一行,两个整数,即题目所要求的两个答案。
提示: USACO Training
限制: 每个测试点1秒
样例:
输入
-----------
3
300 1000
700 1200
1500 2100
输出
-----------
900 300
二、解题方法1
这个方法的思路比较简单。
Step1:先利用数组,以数组下标表示挤奶的时间,以数组值表示是否挤奶(如果此刻有人在挤奶,则值为1,否则值为0)。
Step2:将数组中所有出现状态转换的节点(即相邻状态从0–>1或从1–>0)记录,并存储在一个vector中(记为time)。
Step3:通过time中的转换时间计算得到最长忙碌时间和最长空闲时间。
代码如下:
# include <iostream>
# include <vector>
# include <algorithm>
using namespace std;
//注:最开始的那段空闲时间(即还没有人开始挤奶时),是不需要计算在内的。
int main(){
int N; // N表示奶农的人数
cin >> N;
int max_len = 1000001; //最前面一个和最后面一个为0,为了防止最开始和最后有状态转换
int start_time = 0; //记录输入的奶农挤奶开始时间
int end_time = 0; //记录输入的奶农挤奶结束时间
int *p = new int[max_len];
//初始化数组元素为0,即初始状态,所有的奶农都没有挤奶
for (int i = 0; i < max_len; ++i){ //从0开始,0表示最初的一个,最后一个p[1000000]表示一个冗余的放置0的信息
p[i] = 0;
}
// 将所有奶农挤奶的时间点置为1.
for (int i = 0; i < N; i++){
cin >> start_time >> end_time;
for (int j = start_time + 1; j <= end_time; ++j){
p[j] = 1; //注意,这里时间奶农挤奶时间的左区间置为开,右区间置为闭,在下面time数组中,记录的是奶农最长挤奶时间的左区间的点。
}
}
//计算所有的变换节点,如从挤奶状态变为未挤奶状态,或者从未挤奶状态变为挤奶状态。
vector<int> time;
for (int i = 0; i < max_len - 1; i++){
if ((p[i] == 0 && p[i + 1] == 1) || (p[i] == 1 && p[i + 1] == 0)){
time.push_back(i);
}
}
int max_free_internal = 0; //最长空闲时间
int max_busy_internal = 0; //最长挤奶时间
int internal = 0; //记录挤奶或空闲的持续时间
for (int i = 0; i < (int)time.size()-1; i++){
internal = time[i + 1] - time[i];
//对于time vector而言,第一个减出来的数字都是忙碌状态的
if (i % 2 == 0){ //忙碌状态,忙碌状态和空闲状态是间隔出现的
max_busy_internal = max_busy_internal < internal ? internal:max_busy_internal;
}
else{ //空闲状态
max_free_internal = max_free_internal < internal ? internal:max_free_internal;
}
}
cout << max_busy_internal << " " << max_free_internal << endl;
return 0;
}
运行结果:
Memory: 5584
Time: 32
为了方便对这种解题思路的理解,下面给出了一个简单的示例:
三、解题方法2
本解题方法的思路如下:
Step1:首先将所有奶农的挤奶时间进行排序,按开始时间递增排序,如果开始时间相同,则按照结束时间递增。
Step2:依次遍历已排序的奶农的挤奶时间。并记录挤奶持续的开始时间、结束时间以及最长挤奶时间和最长无人挤奶时间。
①若下一个奶农的挤奶开始时间小于最大挤奶持续的结束时间,则说明这两个奶农的挤奶时间是有重叠的,即最大挤奶持续的结束时间为后一个奶农的挤奶时间或者原来记录的最大挤奶持续的结束时间(谁大取谁)。
②若下一个奶农的挤奶开始时间小于最大挤奶持续的结束时间,即挤奶时间断开了,则需要根据当前奶农的时间更新最长无人挤奶时间,并更新挤奶持续的开始时间、结束时间。
代码如下:
# include <iostream>
# include <algorithm>
using namespace std;
struct Farmer{
int start; //开始挤奶时间
int end; //挤奶结束时间
};
//自定义sort函数的比较函数
bool mycompare(const Farmer &f1, const Farmer &f2){
return f1.start == f2.start ? f1.end < f2.end : f1.start < f2.start;
}
int main(){
int N; //表示奶农的人数
cin >> N;
Farmer *p = new Farmer[N];
for (int i = 0; i < N; i++){
cin >> p[i].start >> p[i].end;
}
sort(p, p + N, mycompare);
int min_busy_time = p[0].start; //表示持续挤奶时间的开始时间
int max_busy_time = p[0].end; //表示持续挤奶时间的最大结束时间
int internal_busy = max_busy_time - min_busy_time; //挤奶最长持续时间
int internal_free = 0; //最长无人挤奶时间
for (int i = 1; i < N; i++){
if (p[i].start <= max_busy_time){ //下一个的开始时间在前一个结束时间之前
max_busy_time = max(p[i].end, max_busy_time); //更新持续挤奶时间的最大结束时间,之所以取p[i].end和max_busy_time的最大值,是考虑p[i].end比max_busy_time还小的情况,所以不能直接更新为p[i].end(可能后一个人挤奶开始时间比前一个人晚、结束时间比前一个人早)。
if (internal_busy < (max_busy_time - min_busy_time)){
internal_busy = max_busy_time - min_busy_time; //更新最长挤奶时间
}
}
else if (p[i].start > max_busy_time){
if (internal_free < (p[i].start - max_busy_time)){
internal_free = p[i].start - max_busy_time; //更新最长无人挤奶时间
}
min_busy_time = p[i].start; //重新开始下一阶段的持续挤奶时间记录
max_busy_time = p[i].end;
}
}
cout << internal_busy << " " << internal_free << endl;
return 0;
}
执行结果:
Memory: 1676
Time: 4