农场主约翰的牛棚有C个连续的牛栏,编号从1到C。现在有M块木板可以用来修补这些牛栏,要求用最少的木板总长度覆盖所有需要修补的牛栏。这是一个典型的区间覆盖问题,我们需要在满足覆盖所有指定牛栏的前提下,最小化使用的木板总长度。
问题的核心在于如何高效地分配有限的木板资源(M块)来覆盖分散的牛栏位置。当M >= C时,解决方案显而易见——每个牛栏用一块长度为1的木板单独覆盖。但当M < C时,就需要采用更聪明的策略来合并覆盖区间。
贪心算法的核心思想是:每次选择当前最优的决策,希望最终达到全局最优。对于这个问题,我们可以按照以下步骤进行:
这种方法的合理性在于:每次我们选择最大的间隔进行分割,能够最大程度地减少总木板长度。这相当于在M次分割机会中,每次都选择能带来最大收益的分割点。
cpp复制#include<bits/stdc++.h>
using namespace std;
bool Compare(int a,int b){
return a>b;
}
int main(){
int m,c;
cin>>m>>c;
vector<int> a(c);
for(int i=0;i<c;i++) cin>>a[i];
sort(a.begin(),a.end());
// 计算单块木板的总长度
int total=a.back()-a.front()+1;
// 特殊情况处理:木板数量足够覆盖每个牛栏
if(m>=c){
cout<<c<<endl;
return 0;
}
vector<int> b; // 存储相邻牛栏间的间隔
for(int i=0;i<a.size()-1;i++){
int temp=a[i+1]-a[i]-1;
b.push_back(temp);
}
// 将间隔从大到小排序
sort(b.begin(),b.end(),Compare);
// 选择前m-1个最大间隔进行分割
for(int i=0;i<m-1;i++){
total-=b[i];
}
cout<<total<<endl;
return 0;
}
a.back()-a.front()+1计算了覆盖所有牛栏所需的最小连续区间长度虽然贪心算法已经提供了优秀的解决方案,但从动态规划的角度思考这个问题也很有启发:
虽然DP解法时间复杂度较高(O(M*C^2)),但对于理解问题本质很有帮助。在实际应用中,贪心算法显然是更优的选择。
这个问题在实际中有很多变种应用,比如:
理解这个问题的解法可以帮助我们解决许多类似的资源分配问题。
我们需要找到所有长度为N的等差数列,其中每一项都可以表示为p²+q²的形式(p,q ≤ M)。这类数被称为双平方数。问题的核心在于高效地生成双平方数集合,并从中找出符合条件的等差数列。
使用unordered_set自动处理重复值是一个明智的选择:
cpp复制unordered_set<int> twodoubles;
for(int p=0;p<=m;p++){
for(int q=0;q<=m;q++){
int num=p*p+q*q;
twodoubles.insert(num);
}
}
这种方法的时间复杂度是O(M²),由于unordered_set的插入操作平均为O(1),整体效率很高。
寻找等差数列的核心思路:
cpp复制vector<pair<int,int>> res;
for(int a:twodoubles){
int max_b=(max_num-a)/(n-1);
if(max_b<1) continue;
for(int b=1;b<=max_b;b++){
bool flag=true;
for(int k=0;k<n;k++){
int current=a+k*b;
if(twodoubles.find(current)==twodoubles.end()){
flag=false;
break;
}
}
if(flag) res.push_back(pair<int,int>(a,b));
}
}
按照题目要求的顺序排序结果:
cpp复制bool Compare(const pair<int,int> &a,const pair<int,int> &b){
if(a.second==b.second){
return a.first<b.first;
}
return a.second<b.second;
}
sort(res.begin(),res.end(),Compare);
双平方数有一些有趣的数学性质:
理解这些性质可以帮助我们设计更高效的算法,或者预测某些等差数列存在的可能性。
这类问题在密码学和编码理论中有实际应用:
理解如何高效生成和验证这类特殊数列,对于计算机科学和数学的交叉领域研究很有帮助。
unordered_set是基于哈希表的实现,提供了平均O(1)时间复杂度的查找、插入和删除操作。在本题中,我们利用它来自动去重:
cpp复制unordered_set<int> twodoubles;
// 插入元素自动去重
twodoubles.insert(num);
注意事项:
当需要对结果排序时,我们将unordered_set中的元素转移到vector中:
cpp复制vector<int> vec(us.begin(), us.end());
pair的使用技巧:
cpp复制// 多种插入方式
res.emplace_back(a,b); // 推荐,效率更高
res.push_back(pair<int,int>(a,b));
res.push_back({a,b}); // C++11统一初始化
对于复杂排序需求,可以自定义比较函数:
cpp复制bool Compare(const pair<int,int> &a,const pair<int,int> &b){
if(a.second==b.second){ // 第二元素相同
return a.first<b.first; // 按第一元素升序
}
return a.second<b.second; // 按第二元素升序
}
在本题中,我们结合了unordered_set的去重优势和vector的排序能力,展示了如何根据不同的操作需求选择合适的容器。
对于大规模数据,考虑使用更快的IO方式:
cpp复制ios::sync_with_stdio(false);
cin.tie(nullptr);
通过系统学习和大量练习,可以掌握更多解决这类问题的技巧和方法。