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 ),这种暴力解法不仅不会超时,反而是逻辑最清晰、最不易出错的满分策略。这提醒我们在做题时要充分利用数据的约束条件。
相关推荐
邪修king1 小时前
C++ 红黑树自平衡核心:旋转变色、规则详解与 STL 选型逻辑
数据结构·c++·b树·算法
随意起个昵称3 小时前
线性dp-计数类题目10(ZBRKA)
算法·动态规划
Navigator_Z9 小时前
LeetCode //C - 1089. Duplicate Zeros
c语言·算法·leetcode
cany10009 小时前
C++ -- 可变参数模板
c++
不会C语言的男孩10 小时前
C++ Primer 第2章:变量和基本类型
开发语言·c++
云泽80811 小时前
C++ 可调用对象通关指南:深度解析 Lambda 表达式、function 包装器与 bind 绑定器
开发语言·c++·算法
wlsh1512 小时前
Go 迭代器
算法
Tri_Function12 小时前
简单图论大学习
c++
语戚12 小时前
力扣 3161. 块放置查询:线段树解法(Java 实现)
java·算法·leetcode·面试·线段树·力扣·