【递归完全搜索】CCC 2008 - 24点游戏Twenty-four
题目描述
“24点”是一种流行的纸牌游戏,适合四名玩家一起玩。
每位玩家有一叠面朝下的牌。每轮游戏中,四位玩家各自翻开自己牌堆的顶牌,让所有人可见。游戏目标是用这四张牌的数值(A=1,J=11,Q=12,K=13
)构造一个算式,使其结果等于 242424。
((A∗K)−J)∗Q((A * K) - J) * Q ((A∗K)−J)∗Q
((1∗13)−11)∗12((1 * 13) - 11) * 12 ((1∗13)−11)∗12
例如,题目插图中的示例中,可以构造出一个算式使结果为 242424。
第一个找到这种算式的玩家赢得本轮,并将这四张牌都放到自己牌堆的底部。
每个合法的算式必须:
-
恰好使用这四张牌的数值;
-
只能使用加法、减法、乘法或除法;
-
可以使用括号改变运算顺序;
-
不能将多张牌拼接成多位数(如 222 和 444 拼成 242424 是禁止的);
-
除法的结果必须是整数(包括算式中任意子表达式的中间结果也必须为整数)。
在某些情况下,玩家可能很久也找不到等于 242424 的表达式,甚至可能根本不存在这样的表达式。
你的任务是:给定四张牌,找到一个算式,使结果是 不超过 242424 的最大整数。
输入格式
第一行:一个整数 nnn,表示有多少组牌。
接下来每组牌包含 444 行,每行一个整数,表示一张牌的值。
输出格式
对于每组牌,输出一行一个整数,表示用这 444 张牌能组合出的、不超过 242424 的最大值。
样例输入
3
3
3
3
3
1
1
1
1
12
5
13
1
样例输出
24
4
21
提交链接
Twenty-four
思路分析
-
全排列枚举牌的顺序
- 因为牌的排列顺序会影响运算结果(例如 333 - 555 和 555 - 333 不同)。
- 444 张牌的所有排列数为 4!=244! = 244!=24 种。
-
枚举运算符组合
- 444 张牌有 333 个空隙,每个空隙可以放
+ - * /
四种运算符。 - 一共有 43=644^3 = 6443=64 种运算符组合。
- 444 张牌有 333 个空隙,每个空隙可以放
-
枚举括号(运算顺序)
- 四个数、三个运算符,合法的运算顺序(括号摆放方式)有 555 种:
((a op1 b) op2 c) op3 d
(a op1 (b op2 c)) op3 d
a op1 ((b op2 c) op3 d)
a op1 (b op2 (c op3 d))
(a op1 b) op2 (c op3 d)
- 四个数、三个运算符,合法的运算顺序(括号摆放方式)有 555 种:
-
整数除法检查
- 除法必须整除且不能除以零,运算时要判断。
-
更新最大值
- 如果某个算式结果 ≤24≤ 24≤24 且合法,就用它来更新最大值。
- 全局变量与工具函数
-
vector<char> b{'+', '-', '*', '/'}
- 存四种运算符,方便用下标枚举。
-
int mx
- 记录当前测试用例中最大的不超过 242424 的结果。
-
int cal(int x, char op, int y, bool &ok)
- 执行一次二元运算
x op y
。
ok
用来标记运算是否合法:- 如果是
/
运算,检查除数y
是否为 0; - 检查是否整除
x % y == 0
;
- 如果是
- 如果不满足条件,
ok = false
,表示该运算链无效。
- 执行一次二元运算
- 读取数据
int t;
cin >> t;
while (t--)
{vector<int> a(4);for (int &i : a) cin >> i;
}
- 读取
t
组测试数据。 - 每组数据读入 4 张牌的值。
- 枚举牌的顺序
sort(a.begin(), a.end());
mx = 0;
do
{// ...
} while (next_permutation(a.begin(), a.end()));
sort
保证next_permutation
从最小字典序开始枚举,确保不会漏掉排列。next_permutation
会枚举 444 张牌的所有排列(242424 种)。- 每次排列会进入一次运算符与括号的枚举。
- 枚举运算符
for (int i = 0; i < 4; i++)
{for (int j = 0; j < 4; j++){for (int k = 0; k < 4; k++){char op1 = b[i], op2 = b[j], op3 = b[k];
i, j, k
分别表示三个位置的运算符,取值0~3
,对应+ - * /
。- 一共 646464 种运算符组合。
- 枚举括号方式并计算
- 每种括号方式都要单独用一个
ok = true
,防止上一次计算失败状态影响下一种括号结构。
(1) ((a op1 b) op2 c) op3 d
ok = true;
r = cal(cal(cal(a[0], op1, a[1], ok), op2, a[2], ok), op3, a[3], ok);
if (ok && r <= 24) mx = max(mx, r);
-
按括号从内到外计算。
-
如果
ok
最终还是true
,表示整个计算合法。
(2) (a op1 (b op2 c)) op3 d
ok = true;
r = cal(cal(a[0], op1, cal(a[1], op2, a[2], ok), ok), op3, a[3], ok);
if (ok && r <= 24) mx = max(mx, r);
- 先算
(b op2 c)
,再算a op1 (...)
。
(3) a op1 ((b op2 c) op3 d)
ok = true;
r = cal(a[0], op1, cal(cal(a[1], op2, a[2], ok), op3, a[3], ok), ok);
if (ok && r <= 24) mx = max(mx, r);
- 先算
(b op2 c)
,再(结果 op3 d)
,最后a op1 (...)
。
(4) a op1 (b op2 (c op3 d))
ok = true;
r = cal(a[0], op1, cal(a[1], op2, cal(a[2], op3, a[3], ok), ok), ok);
if (ok && r <= 24) mx = max(mx, r);
- 先算
(c op3 d)
,再(b op2 结果)
,最后a op1 (...)
。
(5) (a op1 b) op2 (c op3 d)
ok = true;
r = cal(cal(a[0], op1, a[1], ok), op2, cal(a[2], op3, a[3], ok), ok);
if (ok && r <= 24) mx = max(mx, r);
- 左右两边分别算,再用
op2
连接。
参考代码
#include <bits/stdc++.h>
using namespace std;vector<char> b{'+', '-', '*', '/'};int mx;int cal(int x, char op, int y, bool &ok)
{if (op == '+')return x + y;else if (op == '-')return x - y;else if (op == '*')return x * y;else{if (y != 0 && x % y == 0)return x / y;else{ok = false;return 0;}}
}
int main()
{int t;cin >> t; // t组样例while (t--){vector<int> a(4);for (int &i : a)cin >> i;sort(a.begin(), a.end());mx = 0;do{// 枚举运算符的使用for (int i = 0; i < 4; i++){for (int j = 0; j < 4; j++){for (int k = 0; k < 4; k++){char op1 = b[i], op2 = b[j], op3 = b[k]; // 三个运算符bool ok;int r;//((a op1 b) op2 c) op3 dok = true;r = cal(cal(cal(a[0], op1, a[1], ok), op2, a[2], ok), op3, a[3], ok);if (ok && r <= 24)mx = max(mx, r);//(a op1 (b op2 c)) op3 dok = true;r = cal(cal(a[0], op1, cal(a[1], op2, a[2], ok), ok), op3, a[3], ok);if (ok && r <= 24)mx = max(mx, r);// a op1 ((b op2 c) op3 d)ok = true;r = cal(a[0], op1, cal(cal(a[1], op2, a[2], ok), op3, a[3], ok), ok);if (ok && r <= 24)mx = max(mx, r);// a op1 (b op2 (c op3 d))ok = true;r = cal(a[0], op1, cal(a[1], op2, cal(a[2], op3, a[3], ok), ok), ok);if (ok && r <= 24)mx = max(mx, r);//(a op1 b) op2 (c op3 d)ok = true;r = cal(cal(a[0], op1, a[1], ok), op2, cal(a[2], op3, a[3], ok), ok);if (ok && r <= 24)mx = max(mx, r);}}}} while (next_permutation(a.begin(), a.end())); // 四个数字的排列cout << mx << endl;}return 0;
}