1. 问题背景与理解
这道题目描述的是在两个城市之间的高速公路上设置路标的问题。公路的起点和终点已经固定有路标,但中间的路标可能不足,导致相邻路标之间的距离过大。我们需要在给定的限制条件下,通过新增不超过K个路标,使得所有相邻路标之间的最大距离(称为"空旷指数")尽可能小。
这个问题可以抽象为:在一个有序的点集(已有路标位置)中插入不超过K个新点,使得所有相邻点之间的最大距离最小化。这是一个典型的优化问题,我们需要找到最优的插入策略。
2. 解题思路分析
2.1 暴力解法不可行
最直观的想法是尝试所有可能的插入方式,计算每种情况下的最大间隔,然后取最小值。然而,这种方法的时间复杂度极高,对于N=1e5和K=1e5的数据规模来说,完全不可行。
2.2 二分答案法
观察到这个问题具有单调性:如果我们设定一个目标最大间隔D,那么:
- 如果D足够大,我们可能不需要插入任何路标就能满足
- 如果D很小,我们可能需要插入很多路标才能满足
这种特性提示我们可以使用二分查找来确定最小的可行D。具体来说:
- 确定二分查找的范围:最小可能D是1,最大可能D是公路长度L
- 对于每个中间值mid,检查是否可以通过插入不超过K个路标,使得所有相邻路标间隔不超过mid
- 根据检查结果调整查找范围
2.3 检查函数的实现
检查函数的核心是计算:对于给定的目标间隔D,最少需要插入多少个路标才能使所有间隔≤D。这可以通过遍历所有现有间隔,对每个间隔计算需要插入的路标数:
对于一个长度为gap的间隔,需要插入的路标数为⌈gap/D⌉-1
将所有间隔需要的路标数相加,如果总和≤K,则D可行。
3. 代码实现详解
3.1 输入处理
cpp复制cin >> len >> n >> k;
signposts.resize(n + 1);
for(int i = 1; i <= n; i++){
cin >> signposts[i];
}
这里读取公路长度len,原有路标数量n,可新增路标数k。然后读取n个路标的位置,存储在vector中。
3.2 二分查找框架
cpp复制l = 1, r = len;
while(l <= r){
mid = l + (r - l) / 2;
if(check(mid)){
ans = mid;
r = mid - 1;
}
else{
l = mid + 1;
}
}
标准的二分查找结构,l和r初始化为可能的最小和最大值。每次取中间值mid,检查是否可行,根据结果调整搜索范围。
3.3 检查函数实现
cpp复制bool check(int ept){
cost = 0;
for(int i = 1; i < n; i++){
cost += (signposts[i + 1] - signposts[i] - 1) / ept;
}
return cost <= k;
}
这里ept是当前尝试的目标间隔。对于每对相邻路标,计算它们之间的间隔,然后计算需要插入的路标数。注意这里用(signposts[i+1]-signposts[i]-1)/ept而不是⌈gap/ept⌉-1,因为整数除法已经实现了类似效果。
4. 算法正确性证明
4.1 单调性证明
对于任何D'≥D,如果在D下需要k个路标,那么在D'下需要的路标数≤k。因为更大的间隔意味着每个间隔需要的路标数不会增加。
4.2 最优性证明
二分查找会找到最小的D,使得需要的路标数≤K。任何比这个D小的值都会需要更多路标,因此这个D就是最优解。
5. 复杂度分析
5.1 时间复杂度
二分查找的时间复杂度是O(logL),每次检查需要O(N)时间遍历所有间隔。因此总时间复杂度是O(NlogL),对于N=1e5和L=1e7来说完全可接受。
5.2 空间复杂度
只需要O(N)空间存储路标位置,非常高效。
6. 边界情况处理
6.1 输入已经满足条件
如果原有路标间隔已经很小,可能不需要新增任何路标。算法会正确识别这种情况,因为检查函数会返回true。
6.2 K=0的情况
这时不能新增任何路标,算法会返回原有路标的最大间隔。
6.3 所有路标集中在起点和终点
如样例所示,这种情况下算法会正确计算需要在中间插入路标的位置。
7. 优化与改进
7.1 输入优化
使用快速输入方法可以加速大规模数据的读取:
cpp复制ios::sync_with_stdio(false);
cin.tie(nullptr);
7.2 检查函数优化
可以提前终止检查:如果在遍历过程中累计cost已经超过K,可以立即返回false,不需要继续计算。
7.3 二分边界优化
初始的r可以设为最大间隔而不是公路长度,减少二分查找范围。
8. 实际应用与扩展
这个问题可以推广到许多实际场景:
- 网络基站部署:在已有基站之间新增基站,使得任何位置的信号覆盖不会太差
- 物流仓库选址:在已有仓库之间新增仓库,使得任何区域的配送距离不会过长
- 教育资源分配:在学校稀疏区域新增教学点,使得学生上学距离合理
9. 常见错误与调试
9.1 整数溢出
在计算间隔时,使用int类型可能溢出,特别是当L=1e7时。可以使用long long更安全。
9.2 二分查找终止条件
确保使用l<=r而不是l<r,避免遗漏某些情况。
9.3 检查函数计算
注意间隔计算应该是signposts[i+1]-signposts[i]而不是signposts[i]-signposts[i-1],取决于循环变量范围。
10. 代码实现完整示例
cpp复制#include<iostream>
#include<vector>
using namespace std;
int len, n, k;
vector<int> signposts;
bool check(int max_gap) {
int needed = 0;
for (int i = 0; i < n - 1; ++i) {
int gap = signposts[i+1] - signposts[i];
needed += (gap - 1) / max_gap;
if (needed > k) return false;
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> len >> n >> k;
signposts.resize(n);
for (int i = 0; i < n; ++i) {
cin >> signposts[i];
}
int left = 1, right = len, ans = len;
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
cout << ans << endl;
return 0;
}
这个实现更加简洁,并且包含了前面提到的优化点。检查函数中一旦需要的路标数超过k就立即返回,提高了效率。