题解:洛谷 P1209 修理牛棚
【题目来源】
洛谷:P1209 [USACO1.3] 修理牛棚 Barn Repair - 洛谷
【题目描述】
在一个月黑风高的暴风雨夜,Farmer John 的牛棚的屋顶、门被吹飞了 好在许多牛正在度假,所以牛棚没有住满。
牛棚一个紧挨着另一个被排成一行,牛就住在里面过夜。有些牛棚里有牛,有些没有。所有的牛棚有相同的宽度。
自门遗失以后,Farmer John 必须尽快在牛棚之前竖立起新的木板。他的新木材供应商将会供应他任何他想要的长度,但是吝啬的供应商只能提供有限数目的木板。Farmer John 想将他购买的木板总长度减到最少。
给出 m,s,c,表示木板最大的数目、牛棚的总数、牛的总数;以及每头牛所在牛棚的编号,请算出拦住所有有牛的牛棚所需木板的最小总长度。
【输入】
一行三个整数 m,s,c,意义如题目描述。接下来 c 行,每行包含一个整数,表示牛所占的牛棚的编号。
【输出】
输出一行一个整数,表示所需木板的最小总长度。
【输入样例】
4 50 18
3
4
6
8
14
15
16
17
21
25
26
27
30
31
40
41
42
43
【输出样例】
25
【核心思想】
-
问题分析:给定 \(m\) 块木板、\(s\) 个牛棚、\(c\) 头牛的位置,要求用不超过 \(m\) 块木板覆盖所有有牛的牛棚,使木板总长度最小。木板覆盖的是连续区间,多块木板覆盖的区间不能重叠。这是一个贪心 + 区间分割问题,关键在于利用"空隙"来减少木板数量。
-
算法选择:
- 排序定位:将牛的位置按编号排序,得到连续覆盖所有牛所需的最小区间 \([a_1, a_c]\)
- 空隙分析:相邻牛之间的空牛棚数 \(d_i = a_{i+1} - a_i - 1\) 是"可节省的长度"——如果在此处断开木板,可以跳过这些空牛棚
- 贪心选最大空隙:木板数从 \(1\) 块增加到 \(m\) 块,意味着可以断开 \(m-1\) 次。每次断开应选择最大的空隙,以最大化节省的总长度
- 等价转化:最小总长度 = 全覆盖长度 \(-\) 最大的 \(m-1\) 个空隙之和
-
关键步骤:
- 读入数据:\(m, s, c\) 和 \(c\) 个牛棚编号 \(a[1..c]\)
- 特判:若 \(m \geq c\),每头牛单独一块木板,答案为 \(c\)
- 排序:将牛棚编号从小到大排序
- 计算空隙:遍历 \(i\) 从 \(2\) 到 \(c\),\(d_{i-1} = a_i - a_{i-1} - 1\)
- 贪心选取:将空隙数组从大到小排序,取前 \(m-1\) 个最大空隙
- 计算答案:\(\text{ans} = (a_c - a_1 + 1) - \sum_{i=1}^{m-1} d_i\)
-
时间/空间复杂度:
- 时间复杂度:\(O(c \log c)\),排序主导
- 空间复杂度:\(O(c)\),存储牛棚位置和空隙数组
-
贪心分割的核心思想:
- 全覆盖为基准:先用一块木板覆盖所有牛,长度为 \(a_c - a_1 + 1\)
- 空隙即节省:在相邻牛群之间的空隙处断开木板,可以跳过空牛棚,减少总长度
- 优先大空隙:断开位置应选择最大的空隙,因为每多一块木板只能减少一个断点,必须让每次断开都产生最大效益
- 木板数与断点数关系:\(m\) 块木板需要 \(m-1\) 个断点,因此取最大的 \(m-1\) 个空隙
- 适用于区间覆盖、资源分割、最小化连续段总长度等问题
【解题思路】

【算法标签】
普及 #贪心
【代码详解】
// 使用dfs算法,最后2个测试点TLE
#include <bits/stdc++.h>
using namespace std;
// 全局变量定义
int m; // 木板数量
int s; // 牛棚总长度(未使用)
int c; // 有牛的牛棚数量
int a[205]; // 存储有牛的牛棚编号
int b[205]; // 存储当前分配的木板覆盖的牛棚数
int minn = 1e9; // 最小木板总长度,初始化为极大值
/**
* 深度优先搜索函数,用于尝试所有可能的木板分配方案
* @param step 当前处理到第几块木板
*/
void dfs(int step)
{
// 计算当前已分配的牛棚总数
int sum = 0;
for (int i = 1; i <= m; i++)
{
sum += b[i];
}
// 剪枝:如果已超过总牛棚数则返回
if (sum > c)
{
return;
}
// 当所有木板都分配完毕时
if (step > m)
{
// 检查是否正好覆盖所有牛棚
if (sum == c)
{
int ans = 0; // 当前方案的总木板长度
int last = 1; // 上一个牛棚的起始位置
// 计算当前分配方案的总长度
for (int i = 1; i <= m; i++)
{
// 计算第i块木板的长度
ans += (a[last + b[i] - 1] - a[last] + 1);
// 剪枝:如果已经超过当前最小值则提前返回
if (ans > (a[c] - a[1] + 1))
{
return;
}
// 更新下一块木板的起始位置
last = last + b[i];
}
// 更新最小总长度
minn = min(minn, ans);
}
return;
}
// 尝试为当前木板分配不同数量的牛棚
for (int i = 1; i <= c - m; i++)
{
b[step] = i; // 尝试分配i个牛棚给当前木板
dfs(step + 1); // 递归处理下一块木板
b[step] = 0; // 回溯,重置分配
}
}
int main()
{
// 输入数据
cin >> m >> s >> c;
for (int i = 1; i <= c; i++)
{
cin >> a[i];
}
// 将牛棚编号排序
sort(a + 1, a + c + 1);
// 特殊情况处理
if (m == 1)
{
// 只有一块木板时,必须覆盖所有牛棚
cout << a[c] - a[1] + 1 << endl;
exit(0);
}
if (m >= c)
{
// 木板数多于牛棚数时,每个牛棚单独一块木板
cout << c << endl;
exit(0);
}
// 开始深度优先搜索
dfs(1);
// 输出最小总长度
cout << minn << endl;
return 0;
}
// 使用贪心重做一遍
#include <bits/stdc++.h>
using namespace std;
int m, s, c, a[205], b[205];
bool cmp(int x, int y)
{
return x>y;
}
int main()
{
cin >> m >> s >> c; // 输入m、s和c(s没有用)
for (int i=1; i<=c; i++) { // 输入牛的位置
cin >> a[i];
}
if (m>=c) { // 如果木板数大于牛的数量
cout << c << endl; // 那么每头牛都可以有一块木板,输出c并退出程序
return 0;
}
sort(a+1, a+c+1); // 对a按照从小到大方式排序
int mark = 1; // 定义b数组的下标
for (int i=2; i<=c; i++) { // 从第2头牛所在的牛棚开始遍历
if (a[i]-a[i-1]>1) { // 如果两头牛之间有间隙
b[mark++] = a[i]-a[i-1]-1; // 计算空隙
}
}
sort(b+1, b+mark, cmp); // 对间隙按照从大到小方式排序
int len = a[c]-a[1]+1; // 算出整个板的长度
for (int i=1; i<=m-1; i++) { // 用总长度减去每个空隙的长度,这里最多m-1个空隙(减少1个空隙就需要2块木板,减少2个空隙就需要3块木板...)
len = len - b[i];
}
cout << len << endl; // 输出最后的长度,就是多块木板的总长
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int m, s, c, ans; // m: 木板数, s: 牛棚总数, c: 牛的数量
int a[205]; // 牛所占的牛棚的编号
int d[205]; // 相邻牛之间的空牛棚数
int main()
{
cin >> m >> s >> c; // 输入木板数, 牛棚总数, 牛的数量
for (int i = 1; i <= c; i++) // 输入每头牛所在的牛棚编号
{
cin >> a[i];
}
sort(a + 1, a + c + 1); // 按牛棚编号排序
for (int i = 2; i <= c; i++) // 计算相邻牛之间的空牛棚数
{
d[i - 1] = a[i] - a[i - 1] - 1; // 两牛之间的空牛棚
}
sort(d + 1, d + c); // 对空牛棚数排序
ans = c; // 初始至少需要c块木板(每头牛一块)
if (m < c) // 如果木板数少于牛数
{
for (int i = 1; i <= c - m; i++) // 需要合并c-m个间隔
{
ans += d[i]; // 加上间隔中的空牛棚
}
}
cout << ans; // 输出最小木板总长度
return 0;
}
【运行结果】
4 50 18
3
4
6
8
14
15
16
17
21
25
26
27
30
31
40
41
42
43
25
浙公网安备 33010602011771号