这道题目描述的是多个农民挤奶时间段的统计问题。我们需要处理N个时间段区间,计算两个关键指标:
这本质上是一个经典的区间合并问题。在算法领域,区间合并通常用于处理重叠或相邻的时间段、数值范围等场景。这类问题的核心在于如何高效地识别和处理区间之间的关系。
提示:区间合并问题的关键在于正确识别区间之间的重叠或相邻关系,并设计合理的合并策略。
解决这个问题的标准步骤如下:
排序预处理:首先将所有区间按照开始时间进行排序。这是区间合并问题的标准预处理步骤,可以确保我们能够按时间顺序处理各个区间。
初始化当前区间:将第一个区间设为当前合并区间。
遍历处理后续区间:
特殊处理最后一个区间:由于循环中只在遇到不重叠区间时才计算长度,因此需要单独处理最后一个连续区间。
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Time{
int start_time;
int end_time;
};
// 排序比较函数:先按开始时间,再按结束时间
bool compareTime(const Time& a,const Time& b){
if(a.start_time!=b.start_time){
return a.start_time<b.start_time;
}
return a.end_time<b.end_time;
}
int main()
{
int N;
cin>>N;
vector<Time> farmers(N);
for(int i=0;i<N;i++){
cin>>farmers[i].start_time>>farmers[i].end_time;
}
// 关键步骤1:排序
sort(farmers.begin(),farmers.end(),compareTime);
int max_milk_time=0;
int max_empty_time=0;
int current_start=farmers[0].start_time;
int current_end=farmers[0].end_time;
// 关键步骤2:区间合并遍历
for(int i=1;i<N;i++){
if(current_end>=farmers[i].start_time){ // 重叠或相邻
current_end=max(current_end,farmers[i].end_time);
}else{ // 不重叠
int milk_time=current_end-current_start;
if(milk_time>max_milk_time){
max_milk_time=milk_time;
}
int empty_time=farmers[i].start_time-current_end;
if(empty_time>max_empty_time){
max_empty_time=empty_time;
}
current_start=farmers[i].start_time;
current_end=farmers[i].end_time;
}
}
// 关键步骤3:处理最后一个区间
int last_milk_time=current_end-current_start;
if(last_milk_time>max_milk_time){
max_milk_time=last_milk_time;
}
cout<<max_milk_time<<" "<<max_empty_time<<endl;
return 0;
}
忘记排序:这是最常见的错误。如果不先排序,合并逻辑将无法正确工作。
最后一个区间处理遗漏:循环中只在遇到不重叠区间时才计算长度,因此必须单独处理最后一个连续区间。
边界条件处理:
时间复杂度分析:
题目要求生成所有分母不超过N的既约真分数,并按增序输出。既约真分数需要满足:
这涉及到数论中的最大公约数(GCD)概念。两个数互质意味着它们的最大公约数为1。
解决这个问题的步骤可以分为:
双重循环生成所有可能分数:
筛选既约分数:
分数排序:
特殊处理边界情况:
cpp复制#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef struct fraction{
int denominator; // 分母
int numerator; // 分子
}Fraction;
// 分数比较函数
bool compareFraction(const Fraction& a,const Fraction& b){
return a.numerator*b.denominator<b.numerator*a.denominator;
}
// 欧几里得算法求最大公约数
int gcd(int a,int b){
while(b!=0){
int temp=b;
b=a%b;
a=temp;
}
return a;
}
int main()
{
int N;
cin>>N;
vector<Fraction> fractions;
for(int denom=1;denom<=N;denom++){ // 分母
for(int numer=0;numer<denom;numer++){ // 分子
if(numer==0){
if(denom==1){ // 处理0/1的情况
fractions.push_back({1,0});
}
continue;
}
if(gcd(denom,numer)==1){ // 既约分数
fractions.push_back({denom,numer});
}
}
}
// 关键步骤:分数排序
sort(fractions.begin(),fractions.end(),compareFraction);
// 输出结果
for(const auto& frac:fractions){
cout<<frac.numerator<<"/"<<frac.denominator<<endl;
}
return 0;
}
分数比较的实现:
GCD算法选择:
边界情况处理:
性能优化:
区间合并问题在算法竞赛和面试中经常出现,掌握其通用解法非常重要:
排序预处理:几乎所有的区间问题都需要先按起始点排序,有时还需要考虑结束点。
贪心策略:通常按照某种顺序处理区间可以获得最优解。
合并条件判断:
数据结构选择:
涉及分数和数论的问题有一些常用技巧:
避免浮点数运算:
GCD/LCM的应用:
分数表示:
小数据测试:
中间输出:
性能测试:
代码审查重点:
在实际编程练习中,我发现在纸上画出区间合并的过程或者分数的排列顺序,可以大大帮助理解问题和调试代码。对于数学相关的问题,理解其背后的数学原理比单纯记忆代码更重要。