记录139
#include<bits/stdc++.h>
using namespace std;
const int N=30;
int v; // 定义全局变量v,表示需要的维他命种类数
int need[N]; // 记录每种维他命每天需要的最小量
int g; // 定义全局变量g,表示饲料的种数
int feed[N][N]; // 二维数组,feed[i][j]表示第i种饲料含有的第j种维他命量
int path[N]; // 记录当前搜索路径中选择的饲料编号
bool found=false; // 标记是否已经找到了最优解
// 检查当前路径中选择的饲料是否满足所有维他命的需求
bool check(int cnt){
int sum[N]={}; // 临时数组,记录当前组合下每种维他命的总量
for(int i=1;i<=cnt;i++){ // 遍历当前选中的每一种饲料
int id=path[i]; // 获取该饲料的编号
for(int j=1;j<=v;j++){ // 累加该饲料提供的各种维他命
sum[j]+=feed[id][j];
}
}
for(int i=1;i<=v;i++){ // 检查每种维他命是否达标
if(sum[i]<need[i]){ // 如果有任何一种维他命不足
return false; // 返回不合法
}
}
return true; // 所有维他命均满足需求
}
// 深度优先搜索(DFS)函数,cur表示当前正在选第几个,cnt表示总共需要选几个
void dfs(int cur,int cnt){
if(found) return;// 如果已经找到了最优解(因为我们是按数量从小到大搜的,找到即最优)
// 直接剪枝返回
if(cur>cnt){ // 如果已经选够了cnt种饲料,越界收网
if(check(cnt)){ // 检查当前方案是否满足维他命需求
cout<<cnt; // 输出最少的饲料种数
for(int i=1;i<=cnt;i++) cout<<" "<<path[i]; // 按顺序输出选择的饲料编号
found=true; // 标记已找到,后续搜索全部剪枝
}
return; // 结束当前递归分支
}
// 从上一个选择的饲料编号+1开始枚举,保证选择的饲料编号是递增的(字典序最小)
// 剪枝:剩下的饲料数量必须足够凑齐cnt种
for(int i=path[cur-1]+1;i<=g-(cnt-cur+1)+1;i++){
path[cur]=i; // 选择第i种饲料
dfs(cur+1,cnt); // 继续选择下一种
}
}
int main(){ // 主函数入口
ios::sync_with_stdio(false); // 关闭标准流同步,提升输入输出效率
cin.tie(0); // 解除cin与cout的绑定,加快读取速度
cin>>v; // 读入维他命种类数v
for(int i=1;i<=v;i++) cin>>need[i]; // 读入每种维他命的需求量
cin>>g; // 读入饲料种数g
for(int i=1;i<=g;i++){ // 读入每种饲料的维他命含量
for(int j=1;j<=v;j++) cin>>feed[i][j];
}
// 从小到大枚举需要选择的饲料种数(1种,2种,3种...)
for(int i=1;i<=g;i++){
path[0]=0; // 初始化路径起点为0(保证第一个饲料从1开始选)
dfs(1,i); // 尝试选i种饲料
if(found) break;// 如果找到了,直接跳出循环
}
return 0; // 程序正常结束
}
题目传送门
https://www.luogu.com.cn/problem/P1460
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的优快云账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是一道经典的迭代加深搜索(Iterative Deepening Search, IDS)问题。
-
问题转化与策略选择:
题目要求我们选出最少的饲料种数,并且在种数相同的情况下,输出字典序最小的饲料编号组合。由于饲料种数 g≤15g≤15 ,数据规模较小,我们可以直接枚举。但如果我们直接暴力搜索所有子集,很难保证找到的第一个解就是“种数最少”的。因此,我们采用迭代加深的策略:从选 1 种饲料开始尝试,如果找不到可行解,再尝试选 2 种,依此类推。这样,我们找到的第一个可行解,必然就是饲料种数最少的最优解。 -
保证字典序最小:
在 DFS 搜索过程中,我们强制让选择的饲料编号严格递增(即下一个选择的饲料编号必须大于上一个)。这样,当我们从小到大枚举饲料时,自然保证了最终输出的组合是字典序最小的。 -
全局剪枝:
因为我们是按照饲料种数从少到多进行搜索的,一旦在某一层(比如选 k 种)找到了一个可行解,那么它一定是最优的。此时我们可以设置一个全局标志位found,直接终止所有后续的搜索,极大提升效率。
代码分块详细解释
1. 头文件、常量与全局变量定义
#include<bits/stdc++.h>
using namespace std;
const int N=30;
int v; // 定义全局变量 v,表示需要的维他命种类数
int need[N]; // 记录每种维他命每天需要的最小量
int g; // 定义全局变量 g,表示饲料的种数
int feed[N][N]; // 二维数组,feed[i][j] 表示第 i 种饲料含有的第 j 种维他命量
int path[N]; // 记录当前搜索路径中选择的饲料编号
bool found=false; // 标记是否已经找到了最优解
- 详细分析:
path数组是本题记录答案的关键,它用来存储当前 DFS 路径上选中的饲料编号。found是一个全局剪枝变量,配合迭代加深的策略,一旦找到答案就彻底停止搜索。
2. 合法性检查函数(check)
// 检查当前路径中选择的饲料是否满足所有维他命的需求
bool check(int cnt){
int sum[N]={}; // 临时数组,记录当前组合下每种维他命的总量
for(int i=1;i<=cnt;i++){ // 遍历当前选中的每一种饲料
int id=path[i]; // 获取该饲料的编号
for(int j=1;j<=v;j++){ // 累加该饲料提供的各种维他命
sum[j]+=feed[id][j];
}
}
for(int i=1;i<=v;i++){ // 检查每种维他命是否达标
if(sum[i]<need[i]){ // 如果有任何一种维他命不足
return false; // 返回不合法
}
}
return true; // 所有维他命均满足需求
}
- 详细分析:当 DFS 选够了指定数量(
cnt)的饲料后,调用此函数进行验证。它遍历path数组中选中的饲料,将它们对应的维他命含量累加到sum数组中,最后逐一与need数组中的最低需求量进行比较。只要有一种维他命不达标,当前组合即为无效。
3. 核心逻辑:DFS 与字典序保证
// 深度优先搜索(DFS)函数,cur 表示当前正在选第几个,cnt 表示总共需要选几个
void dfs(int cur, int cnt){
if(found) return; // 如果已经找到了最优解,直接剪枝返回
if(cur > cnt){ // 如果已经选够了 cnt 种饲料,越界收网
if(check(cnt)){ // 检查当前方案是否满足维他命需求
cout << cnt; // 输出最少的饲料种数
for(int i=1;i<=cnt;i++) cout << " " << path[i]; // 按顺序输出选择的饲料编号
found = true; // 标记已找到,后续搜索全部剪枝
}
return; // 结束当前递归分支
}
// 从上一个选择的饲料编号+1开始枚举,保证选择的饲料编号是递增的(字典序最小)
// 剪枝:剩下的饲料数量必须足够凑齐 cnt 种
for(int i = path[cur-1] + 1; i <= g - (cnt - cur + 1) + 1; i++){
path[cur] = i; // 选择第 i 种饲料
dfs(cur+1, cnt); // 继续选择下一种
}
}
- 详细分析:这是代码的灵魂。
- 字典序保证:
for循环的起始条件i = path[cur-1] + 1是核心。它强制要求下一个选择的饲料编号必须大于上一个,从而保证了选出的组合是严格递增的,自然满足字典序最小的要求。 - 可行性剪枝:循环的终止条件
i <= g - (cnt - cur + 1) + 1是一个经典的组合数学剪枝。它的意思是:当前还剩cnt - cur + 1个位置需要填充,如果从当前位置i往后数,剩下的饲料总数不够填满这些位置,那么继续枚举i及其后面的数就没有意义了,直接跳出循环。 - 全局最优剪枝:一旦
check通过,输出答案并将found置为true,所有的递归层都会因为开头的if(found) return而迅速退出。
- 字典序保证:
4. 主函数:迭代加深框架
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin >> v; // 读入维他命种类数 v
for(int i=1;i<=v;i++) cin >> need[i]; // 读入每种维他命的需求量
cin >> g; // 读入饲料种数 g
for(int i=1;i<=g;i++){ // 读入每种饲料的维他命含量
for(int j=1;j<=v;j++) cin >> feed[i][j];
}
// 从小到大枚举需要选择的饲料种数(1种,2种,3种...)
for(int i=1;i<=g;i++){
path[0]=0; // 初始化路径起点为 0(保证第一个饲料从 1 开始选)
dfs(1, i); // 尝试选 i 种饲料
if(found) break; // 如果找到了,直接跳出循环
}
return 0;
}
- 详细分析:主函数完美实现了迭代加深的框架。外层
for循环从 1 到 gg 依次枚举需要选择的饲料数量。path[0]=0的初始化是为了配合 DFS 中的path[cur-1] + 1,确保第一种饲料可以从编号 1 开始合法选择。一旦found变为真,立刻break,保证了答案的绝对最优性。
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 迭代加深框架 | 主函数中的 for(int i=1;i<=g;i++) | 从小到大枚举选择的饲料种数 | 保证了找到的第一个解必然是饲料种数最少的最优解 |
| 字典序保证 | i = path[cur-1] + 1 | 强制下一个选择的编号大于上一个 | 避免了重复组合,并天然保证了输出结果的字典序最小 |
| 可行性剪枝 | i <= g - (cnt - cur + 1) + 1 | 限制枚举的上界 | 当剩余饲料不足以凑齐目标数量时提前终止,大幅减少无效搜索 |
| 全局最优剪枝 | if(found) return | 找到答案后立即停止所有递归 | 避免了在找到最优解后继续浪费时间去搜索更差或同级的解 |
| 合法性验证 | check(cnt) 函数 | 累加维他命并比对最低需求 | 在搜索树的叶子节点准确判定当前饲料组合是否有效 |
| 路径记录 | path[N] 数组 | 记录 DFS 过程中选中的饲料编号 | 将搜索状态与最终答案的输出完美解耦 |
736



被折叠的 条评论
为什么被折叠?



