题解:洛谷 P1824 Aggressive cows
【题目来源】
洛谷:P1824 [USACO05FEB] 进击的奶牛 Aggressive Cows G - 洛谷
【题目描述】
农夫约翰建造了一座有 \(n\) 间牛舍的小屋,牛舍排在一条直线上,第 \(i\) 间牛舍在 \(x_i\) 的位置,但是约翰的 \(m\) 头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。约翰决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
【输入】
第一行用空格分隔的两个整数 \(n\) 和 \(m\);
下面 \(n\) 行为 \(n\) 个用空格隔开的整数,表示位置 \(x_i\)。
【输出】
一行一个整数,表示最大的最小距离值。
【输入样例】
5 3
1 2 8 4 9
【输出样例】
3
【核心思想】
-
问题分析:给定 \(n\) 个牛舍的直线位置和 \(m\) 头牛,需要将 \(m\) 头牛放入牛舍,使得任意两头牛之间的最小距离最大化。这是一个整数二分 + 贪心验证问题,关键在于将"最大化最小距离"转化为二分判定问题,用贪心策略验证给定距离 \(d\) 是否可行。
-
算法选择:
- 整数二分:在距离范围 \([1, \max(x_i) - \min(x_i)]\) 内二分查找最大的可行最小距离
- 贪心验证:对于给定的候选距离 \(mid\),从左到右贪心放置牛:第一头放最左,之后每头放在距离上一头至少 \(mid\) 的最左位置,统计能放多少头
-
关键步骤:
- 读取输入:\(n, m\) 和 \(n\) 个位置 \(x_i\)
- 排序:
sort(a+1, a+n+1),使位置有序 - 二分初始化:\(l = 1\),\(r = a[n] - a[1]\)(最大可能距离)
- 二分过程(\(l < r\)):
- \(mid = l + (r - l) / 2\)
- 贪心验证:
last = a[1],\(cnt = 1\)(第一头放最左)- 遍历 \(i\) 从 \(2\) 到 \(n\):若 \(a[i] - last \ge mid\),则 \(cnt \mathrel{+}= 1\),
last = a[i]
- 判定:
- 若 \(cnt \ge m\):距离 \(mid\) 可行,尝试更大,\(l = mid + 1\)
- 若 \(cnt < m\):距离 \(mid\) 不可行,尝试更小,\(r = mid\)
- 输出 \(l - 1\)(最终 \(l\) 指向第一个不可行的距离)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log (\max x_i))\),二分 \(O(\log (\max x_i))\) 轮,每轮贪心验证 \(O(n)\)
- 空间复杂度:\(O(n)\),存储位置数组
-
整数二分 + 贪心验证的核心思想:
- 单调性:若距离 \(d\) 可行(能放 \(m\) 头牛),则所有 \(\le d\) 的距离也可行;若 \(d\) 不可行,则所有 \(> d\) 的距离均不可行。满足二分条件
- 贪心最优性:对于固定的 \(d\),从左到右尽可能早地放牛,能为后续留下更多空间,是验证 \(d\) 是否可行的最优策略
- 最大化最小值的经典框架:"最大化最小距离/最小化最大距离"类问题通常转化为二分 + 贪心验证
- 适用于"资源分配 + 间距约束"类问题,核心是利用单调性二分搜索并用贪心验证可行性
【解题思路】

【算法标签】
普及- #整数二分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, m, l, r, mid;
int a[100005];
int main()
{
cin >> n >> m; // 输入n和m
for (int i=1; i<=n; i++) { // 输入n个牛舍的位置
cin >> a[i];
}
sort(a+1, a+n+1); // 按照从小到大排列,因为后面需要二分搜索
l = a[1], r = a[n]; // 定义l和r
while (l<r) { // 二分模板
int cnt=0; // 统计可选择的牛舍
mid = l + (r-l)/2; // 二分模板
int last = a[1]; // 定义待比较的牛舍,初始从第1个牛舍开始比较
for (int i=2; i<=n; i++) { // 往后遍历所有牛舍
if (a[i]-last>=mid) { // 如果两个牛舍之间的距离大于等于mid,即可以被选择
cnt++; // 统计数自增1
last = a[i];
}
}
// cout << "cnt " << cnt << endl;
if (cnt<m-1) r = mid;
else l = mid+1;
}
cout << l-1 << endl; // 因为r先动,所以最终输出应该是l-1(l最后+1试探失败,所以需要l-1,即为mid)
return 0;
}
【运行结果】
5 3
1 2 8 4 9
3
浙公网安备 33010602011771号