题解:洛谷 P10377 好斗的牛
【题目来源】
洛谷:P10377 [GESP202403 六级] 好斗的牛 - 洛谷
【题目描述】
你有 \(10^9\) 个牛棚,从左到右一字排开。你希望把 \(n\) 头牛安置到牛棚里。麻烦的是,你的牛很好斗,如果他们附近有其他的牛,他们就会不安分地去挑事。其中,第 \(i\) 头牛的攻击范围是 \((a_i, b_i)\),这意味着,如果他的左边 \(a_i\) 个牛棚或者右边 \(b_i\) 个牛棚有其他牛,它就会去挑事。
你想留下一段连续的牛棚,并把其他牛棚都卖掉。请问您最少需要留下多少牛棚,才能保证至少存在一种方案能够把所有的 \(n\) 头牛都安置进剩余的牛棚里,且没有牛会挑事?
【输入】
第一行一个正整数 \(n\)。
第二行 \(n\) 个正整数 \(a_1, a_2, \dots a_n\)。
第三行 \(n\) 个正整数 \(b_1, b_2, \dots b_n\)。
【输出】
输出一行一个整数表示答案。
【输入样例】
2
1 2
1 2
【输出样例】
4
【核心思想】
-
问题分析:有 \(n\) 头牛,每头牛有攻击范围 \((a_i, b_i)\),即左边 \(a_i\) 个牛棚和右边 \(b_i\) 个牛棚内不能有其他牛。需要找到一种牛的排列顺序和间距,使得所有牛安置所需的最少连续牛棚数。这是一个全排列枚举 + 贪心间距计算问题,核心在于枚举所有牛的排列顺序,计算每种排列所需的最小总距离。
-
算法选择:
- DFS 全排列:枚举 \(n\) 头牛的所有排列顺序(\(n!\) 种)
- 贪心间距计算:对于给定排列,相邻两头牛 \(i\)(左)和 \(j\)(右)之间的最小安全距离为 \(\max(a_j, b_i) + 1\)(间隔 \(\max(a_j, b_i)\) 个空牛棚,再加 \(1\) 个牛棚放牛 \(j\))
-
关键步骤:
- 读入数据:读取 \(n\) 和数组 \(a[1..n], b[1..n]\)
- DFS 生成全排列(\(dfs(step)\)):
- 若 \(step > n\):计算当前排列的总距离
- \(tot \leftarrow 1\)(第一头牛占 \(1\) 个牛棚)
- 遍历 \(i\) 从 \(2\) 到 \(n\):
- \(tot \leftarrow tot + \max(cow[a[i]].a, cow[a[i-1]].b) + 1\)
- \(minn \leftarrow \min(minn, tot)\)
- 否则:枚举未使用的牛,标记后递归,回溯
- 若 \(step > n\):计算当前排列的总距离
- 输出结果:\(minn\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n! \cdot n)\),全排列枚举 \(n!\) 种,每种计算距离 \(O(n)\)
- 空间复杂度:\(O(n)\),递归栈和标记数组
-
全排列枚举与贪心间距的核心思想:
- 相邻牛的安全距离:对于排列中相邻的牛 \(p\)(左)和 \(q\)(右),牛 \(q\) 的左边需要 \(a_q\) 个空牛棚,牛 \(p\) 的右边需要 \(b_p\) 个空牛棚。两者共享中间区域,因此最小间隔为 \(\max(a_q, b_p)\) 个空牛棚,加上牛 \(q\) 自身占的 \(1\) 个牛棚
- 全排列的必要性:牛的排列顺序直接影响总距离,不同顺序下相邻关系不同,需要枚举所有可能找到最优解
- DFS 回溯:通过标记数组 \(flag\) 实现排列生成,递归后回溯取消标记,确保每种排列只被考虑一次
- 第一头牛的特殊处理:第一头牛没有左边的牛,只需 \(1\) 个牛棚。后续每头牛需要 \(\max(a_{current}, b_{previous}) + 1\) 个牛棚(间隔 + 自身位置)
- 适用于小规模排列优化、相邻约束类 DFS 枚举问题
【算法标签】
普及- #DFS-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n; // 牛的数量
int flag[15]; // 标记数组,用于记录牛是否被使用
int a[15]; // 排列数组,存储当前排列的牛的顺序
int minn = 1e9; // 最小总距离,初始化为一个大数
struct Node
{
int a; // 牛的左边距离
int b; // 牛的右边距离
}cow[15]; // 牛的信息数组
// 深度优先搜索生成全排列
void dfs(int step)
{
// 如果已经排列完所有牛,计算当前排列的总距离
if (step > n)
{
int tot = 1; // 初始化总距离,第一头牛需要一个牛棚
for (int i = 2; i <= n; i++) // 从第二头牛开始计算
{
// 计算相邻两头牛之间的栅栏距离
// 取左边牛的右边距离和右边牛的左边距离的最大值
tot += max(cow[a[i]].a, cow[a[i-1]].b);
tot += 1; // 当前这头牛也需要一个牛棚
}
// 更新最小总距离
minn = min(minn, tot);
return;
}
// 生成全排列
for (int i = 1; i <= n; i++) // 尝试将每头牛放在当前位置
{
if (!flag[i]) // 如果这头牛还没有被使用
{
flag[i] = 1; // 标记为已使用
a[step] = i; // 将牛i放在第step个位置
dfs(step + 1); // 递归放置下一头牛
flag[i] = 0; // 回溯,取消标记
}
}
}
int main()
{
// 输入牛的数量
cin >> n;
// 输入每头牛左边的距离
for (int i = 1; i <= n; i++)
{
cin >> cow[i].a;
}
// 输入每头牛右边的距离
for (int i = 1; i <= n; i++)
{
cin >> cow[i].b;
}
// 从第一个位置开始深度优先搜索
dfs(1);
// 输出最小总距离
cout << minn << endl;
return 0;
}
【运行结果】
2
1 2
1 2
4
浙公网安备 33010602011771号