题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(m\) 块木板、\(s\) 个牛棚、\(c\) 头牛的位置,要求用不超过 \(m\) 块木板覆盖所有有牛的牛棚,使木板总长度最小。木板覆盖的是连续区间,多块木板覆盖的区间不能重叠。这是一个贪心 + 区间分割问题,关键在于利用"空隙"来减少木板数量。

  2. 算法选择

    • 排序定位:将牛的位置按编号排序,得到连续覆盖所有牛所需的最小区间 \([a_1, a_c]\)
    • 空隙分析:相邻牛之间的空牛棚数 \(d_i = a_{i+1} - a_i - 1\) 是"可节省的长度"——如果在此处断开木板,可以跳过这些空牛棚
    • 贪心选最大空隙:木板数从 \(1\) 块增加到 \(m\) 块,意味着可以断开 \(m-1\) 次。每次断开应选择最大的空隙,以最大化节省的总长度
    • 等价转化:最小总长度 = 全覆盖长度 \(-\) 最大的 \(m-1\) 个空隙之和
  3. 关键步骤

    • 读入数据\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(c \log c)\),排序主导
    • 空间复杂度:\(O(c)\),存储牛棚位置和空隙数组
  5. 贪心分割的核心思想

    • 全覆盖为基准:先用一块木板覆盖所有牛,长度为 \(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
posted @ 2026-08-03 08:46  团爸讲算法  阅读(25)  评论(0)    收藏  举报