P10377 [GESP202403 六级] 好斗的牛

记录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 ),这种暴力解法不仅不会超时,反而是逻辑最清晰、最不易出错的满分策略。这提醒我们在做题时要充分利用数据的约束条件。
相关推荐
kisshyshy20 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
众少成多积小致巨1 天前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2123 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2124 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack204 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法