题解:洛谷 P5833 Livestock Lineup

【题目来源】

洛谷:P5833 [USACO19DEC] Livestock Lineup B - 洛谷

【题目描述】

每天,Farmer John 都要给他的 \(8\) 头奶牛挤奶。她们的名字分别是 Bessie,Buttercup,Belinda,Beatrice,Bella,Blue,Betsy,和 Sue。

不幸的是,这些奶牛相当难以伺候,她们要求 Farmer John 以一种符合 \(N\) 条限制的顺序给她们挤奶。每条限制的形式为“\(X\) 必须紧邻着 \(Y\) 挤奶”,要求奶牛\(X\) 在挤奶顺序中必须紧接在奶牛 \(Y\) 之后,或者紧接在奶牛 \(Y\) 之前。

请帮助 Farmer John 求出一种满足所有限制的奶牛挤奶顺序。保证这样的顺序是存在的。如果有多种顺序都满足要求,请输出字典序最小的一种。也就是说,第一头奶牛需要是所有可能排在任意合法奶牛顺序的第一位的奶牛中名字字典序最小的。在所有合法的以这头字典序最小的奶牛为首的奶牛顺序中,第二头奶牛需要是字典序最小的,以此类推。

【输入】

输入的第一行包含 \(N\)。以下\(N\) 行每行包含一句句子,以 "\(X\) must be milked beside \(Y\)" 的格式描述了一条限制,其中 \(X\)\(Y\)为 Farmer John 的某些奶牛的名字(上文列举了八个可能的名字)。

【输出】

请用 \(8\) 行输出一个奶牛的顺序,每行输出一头奶牛的名字,满足所有的限制。如果由多种顺序符合要求,输出字典序最小的奶牛顺序。

【输入样例】

3
Buttercup must be milked beside Bella
Blue must be milked beside Bella
Sue must be milked beside Beatrice

【输出样例】

Beatrice
Sue
Belinda
Bessie
Betsy
Blue
Bella
Buttercup

【核心思想】

  1. 问题分析:给定 \(8\) 头固定名字的奶牛和 \(N\) 条"必须相邻"的约束,求满足所有约束的挤奶顺序,且要求字典序最小。本质是一个带约束的全排列搜索问题,搜索空间为 \(8! = 40320\),完全可接受。

  2. 算法选择

    • DFS全排列 + 剪枝验证:先对奶牛名字按字典序排序,再用深度优先搜索生成所有排列,每生成一个完整排列后验证是否满足所有相邻约束,第一个满足条件的排列即为字典序最小答案
    • 字典序保证:由于初始数组已按字典序排序,DFS按顺序选取奶牛,第一个合法排列自然满足字典序最小
  3. 关键步骤

    • 预处理
      • 定义奶牛名字数组 \(a[1..8]\),按字典序排序
      • 读取 \(N\) 条约束,提取每对相邻奶牛 \((p[i].x, p[i].y)\)
    • DFS排列生成(参数 \(\text{step}\) 表示当前填充位置 \(1..8\)):
      • 终止条件\(\text{step} = 9\)(已生成完整排列),进入验证
      • 验证约束:遍历所有 \(N\) 条约束,在排列 \(b[1..8]\) 中查找 \(p[i].x\)\(p[i].y\) 的位置 \(\text{pos}_1, \text{pos}_2\),若存在 \(|\text{pos}_1 - \text{pos}_2| \neq 1\) 则标记非法
      • 输出与退出:若全部约束满足(\(\text{mark} = 0\)),输出排列并调用 exit(0) 直接终止程序
      • 回溯枚举:对于每个未使用的奶牛 \(i\)\(\text{book}[i] = 0\)),将其放入 \(b[\text{step}]\),标记已使用,递归 \(\text{DFS}(\text{step}+1)\),回溯时恢复状态
    • 字典序保障:由于 \(a\) 数组已排序且按顺序枚举,DFS生成的第一个合法排列即为全局字典序最小
  4. 时间/空间复杂度

    • 时间复杂度:\(O(8! \cdot N)\),最多 \(40320\) 个排列,每个验证 \(O(N)\)
    • 空间复杂度:\(O(8)\),递归栈深度和标记数组
  5. DFS全排列 + 字典序优先的核心思想

    • 搜索顺序即答案顺序:通过预先对候选元素字典序排序,并按该顺序进行DFS分支选择,确保第一个找到的合法解就是字典序最小的解
    • 排列生成的回溯模板:使用 \(\text{book}\) 数组标记已使用元素,标准回溯框架生成无重复全排列
    • 约束验证后置:不进行中剪枝(如部分排列就检查相邻约束),而是生成完整排列后统一验证,简化逻辑(因 \(8!\) 规模极小,无需复杂剪枝)
    • 立即退出策略:找到第一个合法排列后立即 exit(0),避免继续搜索后续排列
    • 适用于小规模全排列搜索、字典序最小解、带简单约束的排序问题

【解题思路】

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n;
string a[9] = {"", "Bessie", "Buttercup", "Belinda", "Beatrice", "Bella", "Blue", "Betsy", "Sue"};
string b[10];
int book[10] = {0};
struct node {
    string x, y;
}p[10];
void dfs(int step)  // 排列模板
{
    if (step==8+1) {  // 排列退出条件
        int mark = 0;  // 定义标记位
        for (int i=1; i<=n; i++) {  // 遍历输入的n对相邻奶牛组合
            int pos1, pos2;  // 查找两头奶牛的位置
            for (int j=1; j<=8; j++) {  // 找到后赋值给pos1和pos2
                if (b[j]==p[i].x) pos1 = j;
                if (b[j]==p[i].y) pos2 = j;
            }
            if (abs(pos1-pos2)!=1) {  // 如果位置之差的绝对值不为1,说明不相邻
                mark = 1;  // 修改mark
                break;  // 退出循环,之后走到return
            }
        }
        if (mark == 0) {  // 如果两层循环下来确定n对奶牛都是相邻的,那说明符合条件
            for (int i=1; i<=8; i++) {  // 输出8头奶牛
                cout << b[i] << endl;  // 注意换行输出
            }
            exit(0);  // 退出程序
        }
        return;
    }
    for (int i=1; i<=8; i++) {  // 排列模板
        if (book[i]==0) {
            b[step] = a[i];  // 这里这里b[step]要赋值为选择的奶牛名称
            book[i] = 1;
            dfs(step+1);
            b[step] = "";  // 还原现场
            book[i] = 0;
        }
    }
}
int main()
{
    cin >> n;  // 输入n
    string tmp;  // 定义临时字符串,接受输入多个字符串中的无效信息
    for (int i=1; i<=n; i++) {  // 遍历n次输入
        cin >> p[i].x >> tmp >> tmp >> tmp >> tmp >> p[i].y;  // 记录相邻的奶牛组合
    }
    sort(a+1, a+8+1);  // 对于8个头奶牛的名字按照字典序排序(这样排列出来的,就一定是按照字典序最小的排前面)
    dfs(1);  // 排列模板
    return 0;
}

【运行结果】

3
Buttercup must be milked beside Bella
Blue must be milked beside Bella
Sue must be milked beside Beatrice
Beatrice
Sue
Belinda
Bessie
Betsy
Blue
Bella
Buttercup
posted @ 2026-08-29 09:38  团爸讲算法  阅读(7)  评论(0)    收藏  举报