题解:洛谷 P1460 健康的荷斯坦奶牛
【题目来源】
洛谷:P1460 [USACO2.1] 健康的荷斯坦奶牛 Healthy Holsteins - 洛谷
【题目描述】
农民 John 以拥有世界上最健康的奶牛为傲。他知道每种饲料中所包含的牛所需的最低的维他命量是多少。请你帮助农夫喂养他的牛,以保持它们的健康,使喂给牛的饲料的种数最少。
给出牛所需的最低的维他命量,输出喂给牛需要哪些种类的饲料,且所需的饲料剂量最少。
维他命量以整数表示,每种饲料最多只能对牛使用一次,数据保证存在解。
【输入】
第一行一个整数 v,表示需要的维他命的种类数。第二行 v 个整数,表示牛每天需要的每种维他命的最小量。
第三行一个整数 g,表示可用来喂牛的饲料的种数。下面 g 行,第 n 行表示编号为 n 饲料包含的各种维他命的量的多少。
【输出】
输出文件只有一行,包括牛必需的最小的饲料种数 p;后面有 p 个数,表示所选择的饲料编号(按从小到大排列)。
如果有多个解,输出饲料序号最小的(即字典序最小)。
【输入样例】
4
100 200 300 400
3
50 50 50 50
200 300 200 300
900 150 389 399
【输出样例】
2 1 3
【核心思想】
-
问题分析:给定 \(v\) 种维他命的最小需求量,以及 \(g\) 种饲料各自含有的维他命量。要求选择最少数量的饲料,使得每种维他命的总量均满足最小需求。若有多解,输出饲料编号字典序最小的方案。这是一个子集枚举 + 可行性验证问题。
-
算法选择:
- DFS 枚举子集:对每个饲料编号 \(pos\),决策"选"或"不选",递归搜索所有可能的子集
- 剪枝优化:当已选数量 \(cnt \ge minSelCnt\) 时提前返回(当前已不可能更优)
- 可行性验证:对选定的饲料子集,累加每种维他命的总量,判断是否全部满足需求
-
关键步骤:
- 读入数据:\(v\)(维他命种类数)、\(v[1..v]\)(最小需求量)、\(g\)(饲料种数)、\(f[i][j]\)(饲料 \(i\) 中维他命 \(j\) 的含量)
- DFS 函数
dfs(pos, cnt):pos:当前考虑的饲料编号cnt:已选择的饲料数量- 终止条件:若 \(pos > g\),调用
check(cnt)验证当前选择,若满足需求且 \(cnt < minSelCnt\),更新最优解 - 选择当前饲料:
sel[cnt+1] = pos,递归dfs(pos+1, cnt+1) - 不选当前饲料:递归
dfs(pos+1, cnt)
- 验证函数
check(selCnt):- 对每种维他命 \(i\),累加所选饲料中该维他命的含量
sum - 若存在
sum < v[i],返回false - 全部满足返回
true
- 对每种维他命 \(i\),累加所选饲料中该维他命的含量
- 输出:最小选择数及对应的饲料编号
-
时间/空间复杂度:
- 时间复杂度:\(O(2^g \cdot v \cdot g)\),枚举 \(2^g\) 个子集,每个验证 \(O(v \cdot g)\)
- 空间复杂度:\(O(g)\),递归深度和选择数组
-
DFS 子集枚举的核心思想:
- 二进制决策模型:每个饲料只有"选"或"不选"两种状态,DFS 按编号顺序枚举形成一棵决策树
- 最优性剪枝:维护当前最优解 \(minSelCnt\),当 \(cnt \ge minSelCnt\) 时无需继续搜索该分支
- 字典序自然保证:按编号 \(1 \sim g\) 顺序决策,先选后递归,首次找到的最小数量解即为字典序最小
- 可行性优先于最优性:先找到任意可行解获得 \(minSelCnt\) 初值,后续搜索以此剪枝
- 适用于子集选择、组合优化、最小覆盖类问题
【解题思路】

【算法标签】
普及- #DFS-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int vType, fType, p;
int v[30], f[20][30], sel[20], ans[20];
int minSelCnt = 20;
bool check(int selCnt) // 定义维他命检查函数
{
for (int i=1; i<=vType; i++) { // 依次遍历所有维他命种类
int sum = 0; // 定义每种维他命的总数,每次循环初始为0
for (int j=1; j<=selCnt; j++) { // 依次遍历选择的那些饲料
sum += f[sel[j]][i]; // sum加上选择的饲料编号的维他命值
}
if (sum<v[i]) { // 如果总和
return false;
}
}
return true;
}
void dfs(int pos, int cnt) // 定义dfs函数,pos为饲料编号,cnt为饲料选择的数量
{
if (pos > fType) { // dfs退出条件,直到选到最后一种饲料编号
if (check(cnt) && cnt < minSelCnt) { // 如果此时选择的饲料的每种维他命之和大于最小维他命需要,且选择数量小于最小选择数量
minSelCnt = cnt; // 更新最小选择数量
for (int i=1; i<=cnt; i++) { // 更新结果数组,即更新为选择的饲料种类
ans[i] = sel[i];
}
}
return; // 一定要写!
}
sel[cnt+1] = pos; // 选择编号为pos的饲料
dfs(pos+1, cnt+1); // 继续选择pos+1编号的饲料,cnt自增1
sel[cnt+1] = 0; // 不选编号为pos的饲料,不选就用0占位(这句可以忽略,因为下一句dfs中cnt不变,下次执行后会被修改)
dfs(pos+1, cnt); // 继续dfs搜索pos+1编号的饲料,但cnt不增加
}
int main()
{
cin >> vType; // 输入需要的维他命种类数
for (int i=1; i<=vType; i++) { // 遍历维他命种类数
cin >> v[i]; // 输入每种维他命的最小量
}
cin >> fType; // 输入饲料种数
for (int i=1; i<=fType; i++) { // for循环遍历饲料种数
for (int j=1; j<=vType; j++) { // for循环遍历维他命种类数
cin >> f[i][j]; // 输入这种饲料各种维他命的值
}
}
dfs(1, 0); // 使用dfs搜索,起始饲料编号为1,0种选择
cout << minSelCnt << " ";
for (int i=1; i<=minSelCnt; i++) {
cout << ans[i] << " ";
}
return 0;
}
【运行结果】
4
100 200 300 400
3
50 50 50 50
200 300 200 300
900 150 389 399
2 1 3
浙公网安备 33010602011771号