每日c/c++题 备战蓝桥杯(修理牛棚 Barn Repair)
修理牛棚 Barn Repair 题解
问题背景与挑战
在一个暴风雨交加的夜晚,Farmer John 的牛棚遭受了严重的破坏。屋顶被掀飞,大门也不翼而飞。幸运的是,许多牛正在度假,牛棚并未住满。然而,为了保护那些还在牛棚里的牛,Farmer John 必须尽快用木板修复牛棚。但是,他的木材供应商是一个吝啬鬼,只能提供有限数量的木板。为了避免浪费资源,Farmer John 希望以最少的木板总长度覆盖所有有牛的牛棚。这是一个典型的优化问题,我们需要在资源有限的情况下,找到最优的解决方案。
输入输出格式详解
输入
输入数据包含两部分:
- 第一行包含三个整数 m、s 和 c,分别表示木板的最大数目、牛棚的总数以及牛的总数。
- 接下来 c 行,每行包含一个整数,表示牛所在的牛棚编号。
输出
输出一个整数,表示覆盖所有有牛的牛棚所需的最小木板总长度。
问题分析与思路探索
这道题是一个典型的区间覆盖问题。我们需要用有限的木板覆盖所有有牛的牛棚,同时使木板的总长度最小。这就好比在一条数轴上,有若干个点(牛棚编号),我们的任务是选择若干个区间(木板的覆盖范围),使这些区间的总长度最短。
我最初的想法是直接暴力枚举所有可能的覆盖方式,但很快意识到这种方法的局限性。随着牛棚数量和木板数量的增加,暴力搜索的时间复杂度呈指数级增长,根本无法在合理的时间内解决问题。于是,我开始思考如何利用动态规划(Dynamic Programming,DP)来优化这个问题。
动态规划是一种将复杂问题分解为简单子问题的算法思想。它通过存储子问题的解,避免重复计算,从而大大提高算法效率。在这个问题中,我们可以定义一个二维数组 f[i][j]
,表示用 j 块木板覆盖前 i 个牛棚的最小总长度。通过对子问题的逐步求解,我们可以最终得到全局最优解。
动态规划的思路与状态转移方程
动态规划表的定义
我们定义 f[i][j]
表示用 j 块木板覆盖前 i 个牛棚的最小总长度。这是一个二维数组,其中 i 表示牛棚的编号,j 表示使用的木板数量。
状态转移方程的推导
对于每个牛棚 i 和木板数目 j,我们有两种选择:
-
放置新的木板:如果在当前牛棚 i 处放置一块新的木板,那么总长度将增加 1(因为每个牛棚的宽度相同),并且使用的木板数量增加 1。这种情况下,状态转移方程为:
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + 1 f[i][j] = f[i - 1][j - 1] + 1 f[i][j]=f[i−1][j−1]+1 -
延长已有木板:如果选择延长已有木板,那么总长度将增加当前牛棚与前一个牛棚之间的距离,即
pp[i] - pp[i - 1]
。这种情况下,状态转移方程为:
f [ i ] [ j ] = f [ i − 1 ] [ j ] + ( p p [ i ] − p p [ i − 1 ] ) f[i][j] = f[i - 1][j] + (pp[i] - pp[i - 1]) f[i][j]=f[i−1][j]+(pp[i]−pp[i−1])
最终,f[i][j]
的值为上述两种情况的较小值:
f [ i ] [ j ] = min ( f [ i − 1 ] [ j − 1 ] + 1 , f [ i − 1 ] [ j ] + ( p p [ i ] − p p [ i − 1 ] ) ) f[i][j] = \min(f[i - 1][j - 1] + 1, f[i - 1][j] + (pp[i] - pp[i - 1])) f[i][j]=min(f[i−1][j−1]+1,f[i−1][j]+(pp[i]−pp[i−1]))
初始化
在动态规划过程中,初始化是非常重要的一步。
-
当没有任何牛棚和木板时,总长度为 0,即:
f [ 0 ] [ 0 ] = 0 f[0][0] = 0 f[0][0]=0 -
对于其他不可达的状态,我们将其初始化为一个很大的值(如
1 << 30
),表示这些状态在初始阶段是不可到达的。
代码实现
以下是基于上述思路的完整代码实现:
#include<bits/stdc++.h>
using namespace std;int m, s, c; // 木板的最大数目、牛棚的总数和牛的数量
int f[205][205] = {0}; // 动态规划表
int pp[205] = {0}; // 牛棚编号数组int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin >> m >> s >> c; // 输入木板数、牛棚总数和牛的数量for (int i = 1; i <= c; ++i) {cin >> pp[i]; // 输入牛所在的牛棚编号}sort(pp + 1, pp + c + 1); // 对牛棚编号进行排序// 初始化DP表for (int i = 1; i <= c; ++i) f[i][0] = 1 << 30;for (int i = 1; i <= m; ++i) f[0][i] = 1 << 30;// 填充DP表for (int i = 1; i <= c; ++i) {for (int j = 1; j <= m; ++j) {f[i][j] = min(f[i - 1][j - 1] + 1, f[i - 1][j] + (pp[i] - pp[i - 1]));}}cout << f[c][m] << endl; // 输出结果return 0;
}
测试与验证
我使用题目中的样例输入对代码进行了测试:
输入:
4 50 18
3
4
6
8
14
15
16
17
21
25
26
27
30
31
40
41
42
43
输出结果为 135
,与题目中的预期结果一致。这表明代码能够正确处理样例输入,并计算出覆盖所有有牛的牛棚所需的最小木板总长度。
总结与拓展
通过动态规划的方法,我们可以高效地解决修理牛棚的问题。动态规划的核心在于将复杂问题分解为简单子问题,利用状态转移方程逐步构建解决方案。在这个过程中,我们不仅需要仔细设计状态表示,还需要合理推导状态转移方程,以确保算法的正确性和高效性。
这种方法不仅适用于这道题,还可以解决类似的区间覆盖问题。通过不断练习和总结,我们可以更好地掌握动态规划的思想和技巧,提升解决复杂问题的能力。希望这篇题解能够帮助你更好地理解动态规划的应用,以及如何在实际问题中灵活运用它。