Livestock Lineup

GitHub跳转原题关系图返回列表

按字典序枚举 8 头奶牛的全排列,检查所有相邻限制,第一个合法排列就是答案。

OJ: usaco

题目 ID: 965

难度:入门

标签:枚举排列模拟

日期: 2026-07-11 14:45

题意

有 8 头固定名字的奶牛,需要安排一个挤奶顺序。

每条限制形如 X must be milked beside Y,表示 XY 必须相邻。

要求输出满足所有限制的字典序最小顺序。

思路

选择序列暴力

可以把 8 个位置看成一串选择:第 dep 个位置选择一头还没用过的奶牛。

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 14:45
 * update_at: 2026-07-11 14:46
 */
// brute.cpp:小数据暴力解,使用选择序列递归枚举所有挤奶顺序。
#include <bits/stdc++.h>
using namespace std;

const int COW_CNT = 8;
const int MAXN = 10;

int n;
string cows[COW_CNT] = {
    "Beatrice",
    "Belinda",
    "Bella",
    "Bessie",
    "Betsy",
    "Blue",
    "Buttercup",
    "Sue"
};

string need_a[MAXN], need_b[MAXN]; // need_a[i] 必须和 need_b[i] 相邻
string choose_cow[COW_CNT];        // choose_cow[dep] 表示第 dep 个位置选择的奶牛
bool used[COW_CNT];
bool found;

int find_pos_in_choose(string name) {
    for (int i = 0; i < COW_CNT; i++) {
        if (choose_cow[i] == name) {
            return i;
        }
    }
    return -1;
}

bool check_order() {
    for (int i = 1; i <= n; i++) {
        int pos_a = find_pos_in_choose(need_a[i]);
        int pos_b = find_pos_in_choose(need_b[i]);
        if (abs(pos_a - pos_b) != 1) {
            return false;
        }
    }
    return true;
}

void dfs_build(int dep) {
    if (found) {
        return;
    }

    if (dep == COW_CNT) {
        if (check_order()) {
            for (int i = 0; i < COW_CNT; i++) {
                cout << choose_cow[i] << '\n';
            }
            found = true;
        }
        return;
    }

    // 第 dep 个位置选择一头还没使用过的奶牛。按字典序枚举,保证第一个合法解最小。
    for (int i = 0; i < COW_CNT; i++) {
        if (used[i]) {
            continue;
        }
        used[i] = true;
        choose_cow[dep] = cows[i];
        dfs_build(dep + 1);
        used[i] = false;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string t;
        cin >> need_a[i] >> t >> t >> t >> t >> need_b[i];
    }

    dfs_build(0);

    return 0;
}

这个暴力按字典序递归生成完整排列,在叶子节点统一检查所有相邻限制。

全排列枚举

因为只有 8 头奶牛:

text
8! = 40320

直接枚举全排列即可。

把 8 个名字先按字典序排好,然后使用 next_permutation 枚举。每次检查所有限制:

text
abs(pos[X] - pos[Y]) == 1

第一个合法排列就是字典序最小答案。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 14:45
 * update_at: 2026-07-11 14:46
 */
#include <bits/stdc++.h>
using namespace std;

const int COW_CNT = 8;
const int MAXN = 10;

int n;
string order_cow[COW_CNT] = {
    "Beatrice",
    "Belinda",
    "Bella",
    "Bessie",
    "Betsy",
    "Blue",
    "Buttercup",
    "Sue"
};

string need_a[MAXN], need_b[MAXN]; // need_a[i] 必须和 need_b[i] 相邻

int find_pos(string name) {
    for (int i = 0; i < COW_CNT; i++) {
        if (order_cow[i] == name) {
            return i;
        }
    }
    return -1;
}

bool check_order() {
    for (int i = 1; i <= n; i++) {
        int pos_a = find_pos(need_a[i]);
        int pos_b = find_pos(need_b[i]);
        if (abs(pos_a - pos_b) != 1) {
            return false;
        }
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string t;
        cin >> need_a[i] >> t >> t >> t >> t >> need_b[i];
    }

    // order_cow 初始就是字典序。按字典序枚举排列,第一个合法排列就是答案。
    do {
        if (check_order()) {
            for (int i = 0; i < COW_CNT; i++) {
                cout << order_cow[i] << '\n';
            }
            return 0;
        }
    } while (next_permutation(order_cow, order_cow + COW_CNT));

    return 0;
}

复杂度

最多枚举 8! 个排列,每个排列检查最多 7 条限制。由于 8 是常数,时间复杂度可以看作 O(1)O(1)

只保存固定数量的名字和限制,空间复杂度为 O(1)O(1)

总结

这题数据范围非常小,重点不是优化,而是利用字典序枚举。

只要初始名字数组按字典序排列,next_permutation 枚举到的第一个合法方案就是题目要求的答案。