题解:洛谷 P8091 Non-Transitive Dice
【题目来源】
洛谷:P8091 [USACO22JAN] Non-Transitive Dice B - 洛谷
【题目描述】
为了消磨牛棚里的时光,奶牛们喜欢玩简单的骰子游戏。其中一种游戏使用两个骰子 \(X\) 和 \(Y\) 进行。两个骰子均被投掷,获胜的骰子是显示的数字较大的骰子。如果两者显示相同的数字,则重新投掷(只要持续打平,骰子可能会被重新投掷多次)。我们称骰子 \(X\) 击败骰子 \(Y\),如果骰子 \(X\) 比骰子 \(Y\) 更有可能赢得这局游戏。
考虑以下的 \(4\) 面骰子:
骰子 \(A\) 在各面上有数字 \(4\),\(5\),\(6\) 和 \(7\)。
骰子 \(B\) 在各面上有数字 \(2\),\(4\),\(5\) 和 \(10\)。
骰子 \(C\) 在各面上有数字 \(1\),\(4\),\(8\) 和 \(9\)。
这些骰子满足一个相当奇妙的性质:\(A\) 击败 \(B\),\(B\) 击败 \(C\),并且 \(C\) 也击败 \(A\)。特别地,三个骰子都不是「最佳的」,可以击败其他两个。在这种情况下,当没有两个骰子打平,也没有一个骰子是最佳的,我们称这三个骰子的集合为「非传递的」。在非传递的三个骰子的集合中,每个骰子击败一个其他骰子,并输给另一个其他骰子。
给定两个 \(4\) 面骰子 \(A\) 和 \(B\) 各面上的数字,请帮助奶牛们求出是否有方法为第三个骰子 \(C\) 的各面分配数字,使得这个骰子的集合是非传递的。所有骰子面上的数字必须是 \(1\) 到 \(10\) 的整数。
【输入】
每个测试用例包含多个独立的子测试用例,必须全部回答正确才能通过整个测试用例。输入的第一行包含\(T(1\le T\le 10)\),为你需要求解的子测试用例的数量。
以下 \(T\) 行,每行描述了一个子测试用例,包含 \(8\) 个整数:骰子 \(A\) 的 \(4\) 面上的整数,以及骰子 \(B\) 的 \(4\) 面上的整数。所有的数均在 \(1\) 到 \(10\) 之间,不一定排序。可能同一个数会出现多次,即使在同一个骰子上也可能出现多个相同的数。
【输出】
输出 \(T\) 行。如果有可能为骰子 \(C\) 分配数字使得第 \(k\) 个测试用例成为一个非传递的骰子集合,则第k行输出 yes,否则输出 no。
【输入样例】
3
4 5 6 7 2 4 5 10
2 2 2 2 1 1 1 1
1 1 1 1 2 2 2 2
【输出样例】
yes
no
no
【核心思想】
-
问题分析:给定两个 \(4\) 面骰子 \(A\) 和 \(B\),判断是否存在第三个 \(4\) 面骰子 \(C\)(面值为 \(1\) 到 \(10\) 的整数),使得三个骰子构成非传递关系:\(A\) 击败 \(B\),\(B\) 击败 \(C\),\(C\) 击败 \(A\)(或反向循环)。本质上是暴力枚举 + 胜负判定问题:枚举所有可能的 \(C\) 骰子组合,检查是否满足非传递条件。
-
算法选择:
- 胜负函数:
f(X, Y)比较骰子 \(X\) 和 \(Y\) 的所有 \(16\) 种面组合,统计 \(X\) 获胜次数是否大于 \(Y\) - 四重循环枚举:\(C\) 骰子的 \(4\) 个面各从 \(1\) 到 \(10\) 枚举,共 \(10^4 = 10000\) 种可能
- 胜负函数:
-
关键步骤:
- 读取数据:读入 \(T\) 组测试数据,每组 \(8\) 个整数(\(A\) 的 \(4\) 面 + \(B\) 的 \(4\) 面)
f(x, y)函数:- 初始化 \(winx = 0, winy = 0\)
- 枚举 \(i = 1\) 到 \(4\),\(j = 1\) 到 \(4\):
- 若 \(x[i] > y[j]\),\(winx++\)
- 若 \(x[i] < y[j]\),\(winy++\)
- 返回 \(winx > winy\)
check(a, b)函数:- 四重循环枚举 \(C\) 的 \(4\) 个面 \((i, j, k, l)\)
- 若
f(a, b) && f(b, c) && f(c, a),返回true - 若
f(b, a) && f(a, c) && f(c, b),返回true - 否则返回
false
- 输出
yes或no
-
时间/空间复杂度:
- 时间复杂度:\(O(T \cdot 10^4 \cdot 16)\),每组数据枚举 \(10000\) 种 \(C\),每种比较 \(16\) 次
- 空间复杂度:\(O(1)\),固定大小的数组
-
暴力枚举的核心思想:
- 非传递性的两种方向:非传递可以是 \(A \to B \to C \to A\) 或 \(A \to C \to B \to A\)(即 \(B \to A \to C \to B\)),需检查两种循环方向
- 骰子胜负的定义:\(X\) 击败 \(Y\) 当且仅当 \(X\) 的面值大于 \(Y\) 的面值的概率严格大于反向概率,即 \(winx > winy\)(平局不计入)
- 枚举空间的可控性:\(10^4\) 种 \(C\) 组合在 \(T \leq 10\) 时完全可接受,无需剪枝优化
- 对称性利用:两种循环方向只需改变比较顺序即可,无需重写逻辑
- 适用于小范围枚举、博弈判定、非传递性验证类问题
【解题思路】

【算法标签】
普及- #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int t, a[5], b[5];
bool f(int x[], int y[]) // 定义确认获胜的函数
{
int winx=0, winy=0; // 定义x和y的获胜次数
for (int i=1; i<=4; i++) { // 依次遍历x和y的16个数字
for (int j=1; j<=4; j++) {
if (x[i]>y[j]) winx++; // 两两进行比较,谁大的话,谁的获胜次数自增1
if (x[i]<y[j]) winy++;
}
}
return winx>winy; // 如果x获胜次数更多,则返回true
}
bool check(int x[], int y[]) // 定义寻找c的函数,注意这里传递的参数为数组
{
int z[5]; // 初始化z数组(即骰子C)
for (int i=1; i<=10; i++) { // 4个面,每个面都遍历1-10
for (int j=1; j<=10; j++) {
for (int k=1; k<=10; k++) {
for (int l=1; l<=10; l++) {
z[1] = i; // 定义z的4个数字
z[2] = j;
z[3] = k;
z[4] = l;
if (f(x, y) && f(y, z) && f(z, x)) return true; // 如果满足x胜y,y胜z,z胜x,则返回true
if (f(y, x) && f(x, z) && f(z, y)) return true; // 如果满足y胜x,x胜z,z胜y,则返回true
}
}
}
}
return false; // 如果都找不到,返回false
}
int main()
{
cin >> t; // 输入t
while (t--) { // 遍历t组数据
for (int i=1; i<=4; i++) { // 记录a的4个数据
cin >> a[i];
}
for (int i=1; i<=4; i++) { // 记录b的4个数据
cin >> b[i];
}
if (check(a, b)) cout << "yes" << endl; // 检查是否可以找到c,满足a胜b,b胜c,c胜a
else cout << "no" << endl;
}
return 0;
}
【运行结果】
3
4 5 6 7 2 4 5 10
yes
2 2 2 2 1 1 1 1
no
1 1 1 1 2 2 2 2
no
浙公网安备 33010602011771号