记录119
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN=15;
int n,a[MAXN],b[MAXN],order[MAXN];
// n为牛的数量;a[i], b[i]存第i头牛的左右攻击范围;order数组记录当前排列中第step头牛的编号
bool vis[MAXN];// 标记数组,vis[i]=1表示第i头牛已经被放入排列中
int min_len=1e9;// 记录最少需要的牛棚数量,初始化为一个很大的数
void dfs(int step){ // step表示当前正在放置第几头牛(从1开始)
if(step>n){// 递归边界:如果 n 头牛全部放置完毕
int cur_len=1;// 当前排列需要的总长度,初始为1(因为第一头牛自己至少占1个牛棚)
for(int i=2;i<=n;i++){// 从第2头牛开始,依次计算它与上一头牛的间隔及自身占位
int last_cow=order[i-1];// 获取上一头牛的编号
int cur_cow=order[i];// 获取当前这头牛的编号
cur_len+=max(a[cur_cow],b[last_cow])+1;
// 累加长度:中间需要的空牛棚数(max(当前牛的左范围, 上一头牛的右范围)) + 当前牛自己占的1个牛棚
}
min_len=min(min_len,cur_len);// 用当前排列算出的长度更新全局最小值
return; // 结束本次递归,返回上一层
}
for(int i=1;i<=n;i++){// 尝试把每一头牛(编号1到n)放在当前的 step 位置
if(vis[i]==0){ // 如果第 i 头牛还没有被使用过
vis[i]=1; // 标记第 i 头牛已使用
order[step]=i;// 记录当前第 step 个位置放的是第 i 头牛
dfs(step+1);// 递归调用,去放置下一头牛(第 step+1 头)
vis[i]=0; // 回溯:取消第 i 头牛的标记,以便在后续循环中尝试其他排列
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
dfs(1);
cout<<min_len;
return 0;//结束程序
}
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
题目传送门
https://www.luogu.com.cn/problem/P10377
思路
1. 题意理解与数学建模
- 攻击范围转化为安全距离 :两头相邻的牛(假设左边是
last_cow,右边是cur_cow)要和平共处,它们之间的空牛棚数量必须同时满足两者的要求。即:空牛棚数 ≥≥b[last_cow]且 空牛棚数 ≥≥a[cur_cow]。因此,它们之间最少需要的空牛棚数为max(b[last_cow], a[cur_cow])。 - 总长度计算:如果确定了牛的排列顺序,第一头牛占据 1 个牛棚,之后每放入一头新牛,都需要增加"安全间隔"加上"自身占位(1个牛棚)"。
2. 算法选择:全排列枚举
- 由于 n≤9n≤9 ,最多有 9!=362880种排列方式。这个数据规模非常小,完全可以通过暴力搜索(DFS)枚举所有可能的排列组合。
- 对于每一种合法的排列,计算出所需的总牛棚数,最后取所有方案中的最小值即可。
代码解析
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN=15;
int n, a[MAXN], b[MAXN], order[MAXN];
// n为牛的数量;a[i], b[i]存第i头牛的左右攻击范围;order数组记录当前排列中第step头牛的编号
bool vis[MAXN];
// 标记数组,vis[i]=1表示第i头牛已经被放入排列中
int min_len=1e9;
// 记录最少需要的牛棚数量,初始化为一个很大的数(INF)
解析 :定义了全局变量和常量。order 数组用于保存当前 DFS 路径上的牛的排列顺序;vis 数组用于防止同一头牛在一次排列中被重复使用。
cpp
void dfs(int step){ // step表示当前正在放置第几头牛(从1开始)
if(step > n){ // 递归边界:如果 n 头牛全部放置完毕
int cur_len = 1; // 当前排列需要的总长度,初始为1(因为第一头牛自己至少占1个牛棚)
for(int i = 2; i <= n; i++){ // 从第2头牛开始,依次计算它与上一头牛的间隔及自身占位
int last_cow = order[i-1]; // 获取上一头牛的编号
int cur_cow = order[i]; // 获取当前这头牛的编号
cur_len += max(a[cur_cow], b[last_cow]) + 1;
// 累加长度:中间需要的空牛棚数(max(当前牛的左范围, 上一头牛的右范围)) + 当前牛自己占的1个牛棚
}
min_len = min(min_len, cur_len); // 用当前排列算出的长度更新全局最小值
return; // 结束本次递归,返回上一层
}
解析 :这是 DFS 的核心部分。当 step > n 时,说明已经成功生成了一个包含 nn 头牛的完整排列。此时通过遍历相邻的牛,利用 max() 函数求出安全间隔并累加,最终得出该排列下的最小牛棚需求,并用它来更新全局最优解 min_len。
cpp
for(int i = 1; i <= n; i++){ // 尝试把每一头牛(编号1到n)放在当前的 step 位置
if(vis[i] == 0){ // 如果第 i 头牛还没有被使用过
vis[i] = 1; // 标记第 i 头牛已使用
order[step] = i; // 记录当前第 step 个位置放的是第 i 头牛
dfs(step + 1); // 递归调用,去放置下一头牛(第 step+1 头)
vis[i] = 0; // 回溯:取消第 i 头牛的标记,以便在后续循环中尝试其他排列
}
}
}
解析 :标准的 DFS 生成全排列模板。在当前层 step,尝试所有尚未使用的牛。进入下一层前打标记(vis[i]=1),从下一层返回后撤销标记(vis[i]=0,即回溯),从而保证能够穷举出所有的排列可能。
cpp
int main(){
ios::sync_with_stdio(false);
cin.tie(0); // IO流加速,提高输入输出效率
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
dfs(1); // 从放置第1头牛开始启动深搜
cout << min_len;
return 0; // 结束程序
}
解析 :主函数负责读取输入数据,构建好 a[] 和 b[] 数组后,直接调用 dfs(1) 启动搜索。最后输出记录下来的最小长度 min_len。
主要知识点总结
1. 深度优先搜索 (DFS) 与回溯算法
- 本题是经典的全排列生成问题 。通过
vis[]状态数组和递归调用,配合回溯操作(撤销标记),可以系统、不重不漏地枚举出所有可能的排列组合。
2. 贪心思想与极值计算
- 在确定了两头牛的相对顺序后,它们之间的安全距离由两者攻击范围的较大值决定(
max(a[cur], b[last]))。这种局部最优的选择构成了全局计算的基石。
3. 状态模拟与边界处理
- 将抽象的"排队问题"转化为具体的"数轴长度计算"。特别要注意边界的初始化,例如第一头牛不需要左侧的安全距离,只需占用 1 个基础长度,这在
cur_len = 1中得到了体现。
4. 复杂度分析与算法适用性
- 虽然暴力枚举的时间复杂度达到了阶乘级 O(N!)O(N!) ,但结合题目给出的极小数据规模( N≤9N≤9 ),这种暴力解法不仅不会超时,反而是逻辑最清晰、最不易出错的满分策略。这提醒我们在做题时要充分利用数据的约束条件。