Non-Transitive Dice

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

枚举第三个骰子的所有 4 个面值,用 16 对面值比较判断是否形成循环胜负。

OJ: usaco

题目 ID: 1180

难度:入门

标签:枚举模拟数学usaco

日期: 2026-07-11 17:50

题意

给定两个 4 面骰子 AB

要求判断是否存在一个 4 面骰子 C,使得三个骰子形成非传递关系:每个骰子都能击败另一个骰子,也会输给另一个骰子。

每个骰子的面值都必须在 1101 \dots 10

思路

先看一个选择序列风格的暴力:

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 17:50
 * update_at: 2026-07-11 17:52
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

int dice_a[4], dice_b[4], dice_c[4];
bool found_answer;

bool beats(int x[], int y[]) {
    int win = 0;
    int lose = 0;

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (x[i] > y[j]) {
                win++;
            }
            if (x[i] < y[j]) {
                lose++;
            }
        }
    }

    return win > lose;
}

bool check_non_transitive() {
    if (beats(dice_a, dice_b) && beats(dice_b, dice_c) && beats(dice_c, dice_a)) {
        return true;
    }
    if (beats(dice_b, dice_a) && beats(dice_a, dice_c) && beats(dice_c, dice_b)) {
        return true;
    }
    return false;
}

// 第 pos 层选择第三个骰子的第 pos 个面。
void dfs_choose_face(int pos) {
    if (found_answer) {
        return;
    }
    if (pos == 4) {
        if (check_non_transitive()) {
            found_answer = true;
        }
        return;
    }

    for (int value = 1; value <= 10; value++) {
        dice_c[pos] = value;
        dfs_choose_face(pos + 1);
    }
}

bool solve_case() {
    for (int i = 0; i < 4; i++) {
        cin >> dice_a[i];
    }
    for (int i = 0; i < 4; i++) {
        cin >> dice_b[i];
    }

    found_answer = false;
    dfs_choose_face(0);
    return found_answer;
}

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

    int t;
    cin >> t;
    while (t--) {
        if (solve_case()) {
            cout << "yes\n";
        } else {
            cout << "no\n";
        }
    }

    return 0;
}

这个暴力把第三个骰子的 4 个面看成 4 层选择,每层枚举 1101 \dots 10。生成完整的 C 后,再检查 A,B,C 是否形成循环胜负。

由于 C 的可能数量只有:

104=10000 10^4 = 10000

所以这个枚举规模很小,可以直接作为满分做法。

判断骰子 X 是否击败骰子 Y 时,枚举所有 16 对面值:

  • X 的点数更大,win++
  • X 的点数更小,lose++
  • 相等时重掷,不计入胜负。

如果 win > lose,则 X 击败 Y

对每个候选骰子 C,检查两种循环方向:

text
A beats B, B beats C, C beats A
B beats A, A beats C, C beats B

只要有一种成立,就输出 yes

代码

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 17:50
 * update_at: 2026-07-11 17:52
 */
#include <bits/stdc++.h>
using namespace std;

int dice_a[4], dice_b[4], dice_c[4];

bool beats(int x[], int y[]) {
    int win = 0;
    int lose = 0;

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (x[i] > y[j]) {
                win++;
            }
            if (x[i] < y[j]) {
                lose++;
            }
        }
    }

    return win > lose;
}

bool check_non_transitive() {
    if (beats(dice_a, dice_b) && beats(dice_b, dice_c) && beats(dice_c, dice_a)) {
        return true;
    }
    if (beats(dice_b, dice_a) && beats(dice_a, dice_c) && beats(dice_c, dice_b)) {
        return true;
    }
    return false;
}

bool solve_case() {
    for (int i = 0; i < 4; i++) {
        cin >> dice_a[i];
    }
    for (int i = 0; i < 4; i++) {
        cin >> dice_b[i];
    }

    for (int a = 1; a <= 10; a++) {
        dice_c[0] = a;
        for (int b = 1; b <= 10; b++) {
            dice_c[1] = b;
            for (int c = 1; c <= 10; c++) {
                dice_c[2] = c;
                for (int d = 1; d <= 10; d++) {
                    dice_c[3] = d;
                    if (check_non_transitive()) {
                        return true;
                    }
                }
            }
        }
    }

    return false;
}

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

    int t;
    cin >> t;
    while (t--) {
        if (solve_case()) {
            cout << "yes\n";
        } else {
            cout << "no\n";
        }
    }

    return 0;
}

复杂度

每组数据枚举 10410^4 个骰子,每次检查是常数规模。

时间复杂度为 O(104)O(10^4),空间复杂度为 O(1)O(1)

总结

本题的关键是看到第三个骰子的搜索空间很小。

直接枚举所有可能骰子,再用 16 对面值比较判断胜负关系,就能稳定解决。