题目链接:洛谷 - P1460 - 健康的和斯坦奶牛
这是一道深搜(枚举子集)的板子题,刚学的时候还是很值得做的。
[USACO2.1] 健康的荷斯坦奶牛 Healthy Holsteins
题目描述
农民 John 以拥有世界上最健康的奶牛为傲。他知道每种饲料中所包含的牛所需的最低的维他命量是多少。请你帮助农夫喂养他的牛,以保持它们的健康,使喂给牛的饲料的种数最少。
给出牛所需的最低的维他命量,输出喂给牛需要哪些种类的饲料,且所需的饲料剂量最少。
维他命量以整数表示,每种饲料最多只能对牛使用一次,数据保证存在解。
输入格式
第一行一个整数
v
v
v,表示需要的维他命的种类数。
第二行
v
v
v 个整数,表示牛每天需要的每种维他命的最小量。
第三行一个整数
g
g
g,表示可用来喂牛的饲料的种数。
下面
g
g
g 行,第
n
n
n 行表示编号为
n
n
n 饲料包含的各种维他命的量的多少。
输出格式
输出文件只有一行,包括牛必需的最小的饲料种数 p p p;后面有 p p p 个数,表示所选择的饲料编号(按从小到大排列)。
如果有多个解,输出饲料序号最小的(即字典序最小)。
输入输出样例 #1
输入 #1
4
100 200 300 400
3
50 50 50 50
200 300 200 300
900 150 389 399
输出 #1
2 1 3
说明/提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
v
≤
25
1\le v \le 25
1≤v≤25,
1
≤
g
≤
15
1\le g \le 15
1≤g≤15。
输入的所有整数在
[
1
,
1000
]
[1,1000]
[1,1000] 范围内。
题目大意:
一头牛每天都需要吃一些维他命,每种的摄入必须达到一定的值(只能多不能少)。
你要做的就是求出最少要用几包饲料。
思路:
因为是找最优解,所以第一眼可能会想到贪心。但是怎么贪呢?显然这道题用不了,因为变量很多,要满足的条件也多,你无法简单地衡量一袋饲料是否要吃。
再看看数据范围:
v
v
v 居然只有
25
25
25 ,除了全状态枚举以外应该没什么题会有这么小的数据了,所以可以用深搜枚举每个饲料是否要吃,当层数到达
g
g
g 的时候判断整体是否可行。
具体怎么判断呢?
其实开个桶数组模拟一下就好了,把所有选了的都加进去,然后判断每项是否都达标。
需要注意的细节是如果有多个解,输出字典序最小的。其实这也很简单,每次先枚举吃这包饲料的,再枚举不吃的,判断的时候只有用的饲料数量小于当前答案时才进行下一步判断。
所以大体是这样的
bool vis[30];
int cnt = 0; //已选了多少种饲料
void dfs(int pos) // 我是以1为入口的。
{
if (pos > v)
{
if (cnt >= ans)
return;
// 模拟过程
// ......
// 判断:
for (int i = 1; i <= v; i++)
if (/*某一项的摄入不达标*/)
return;
// 所有的都满足条件,所以更新答案
// 加入选了的饲料编号
}
// 先枚举吃的(vis是状态数组,表示每袋要不要吃)
vis[pos] = true;
cnt++;
dfs(pos + 1);
// 还原状态,枚举不吃的
vis[pos] = false;
cnt--;
dfs(pos + 1, cnt);
// return;
}
完整代码(实在调不对在来看)
#include <iostream>
#include <vector>
using namespace std;
int v;
int a[30];
int g;
int b[30][30];
bool vis[30];
int sum[30];
int ans = 114514; // 恶臭的答案
int cnt = 0;
vector<int> vec;
bool check() // 因为变量和数组都是全局的,所以就写成函数了,提高可读性
{
// for (int i = 1; i <= v; i++)
// {
// cout << vis[i] << ' ';
// }
// cout << '\n';
for (int i = 1; i <= v; i++)
{
sum[i] = 0;
}
for (int i = 1; i <= g; i++)
{
if (vis[i])
{
for (int j = 1; j <= v; j++)
{
sum[j] += b[i][j];
}
}
}
// for (int i = 1; i <= v; i++)
// {
// cout << sum[i] << ' ';
// }
// cout << '\n';
// 测试用的
for (int i = 1; i <= v; i++)
{
if (sum[i] < a[i])
{
return false;
}
}
return true;
}
void dfs(int pos)
{
if (pos > g)
{
if (cnt < ans && check())
{
ans = cnt;
vec.clear();
for (int i = 1; i <= g; i++)
{
if (vis[i])
{
vec.push_back(i);
}
}
}
return; // 这个return不能省
}
vis[pos] = true;
cnt++;
dfs(pos + 1);
vis[pos] = false;
cnt--;
dfs(pos + 1);
return; // 这个是个人习惯,不写也行
}
int main()
{
// 朴实无华的输入
cin >> v;
for (int i = 1; i <= v; i++)
{
cin >> a[i];
}
cin >> g;
for (int i = 1; i <= g; i++)
{
for (int j = 1; j <= v; j++)
{
cin >> b[i][j];
}
}
// 深搜入口
dfs(1);
// 朴实无华的输出
cout << ans << ' ';
for (auto it : vec) // 对于 C++98 及以前:for (int i = 0; i < vec.size(); i++)
{
cout << it << ' ';
}
// cout << '\n';
return 0;
}
告诉你个小秘密
这道题的样例特别水,我一开始把 dfs 出口的 g 写成 v了都 90分。