【递归完全搜索】CCC 2008 - 24点游戏Twenty-four

题目描述

"24点"是一种流行的纸牌游戏,适合四名玩家一起玩。

每位玩家有一叠面朝下的牌。每轮游戏中,四位玩家各自翻开自己牌堆的顶牌,让所有人可见。游戏目标是用这四张牌的数值(A=1,J=11,Q=12,K=13)构造一个算式,使其结果等于 24 24 24。

( ( A ∗ K ) − J ) ∗ Q ((A * K) - J) * Q ((A∗K)−J)∗Q
( ( 1 ∗ 13 ) − 11 ) ∗ 12 ((1 * 13) - 11) * 12 ((1∗13)−11)∗12

例如,题目插图中的示例中,可以构造出一个算式使结果为 24 24 24。

第一个找到这种算式的玩家赢得本轮,并将这四张牌都放到自己牌堆的底部。

每个合法的算式必须:

  • 恰好使用这四张牌的数值;

  • 只能使用加法、减法、乘法或除法;

  • 可以使用括号改变运算顺序;

  • 不能将多张牌拼接成多位数(如 2 2 2 和 4 4 4 拼成 24 24 24 是禁止的);

  • 除法的结果必须是整数(包括算式中任意子表达式的中间结果也必须为整数)。

在某些情况下,玩家可能很久也找不到等于 24 24 24 的表达式,甚至可能根本不存在这样的表达式。

你的任务是:给定四张牌,找到一个算式,使结果是 不超过 24 24 24 的最大整数。

输入格式

第一行:一个整数 n n n,表示有多少组牌。

接下来每组牌包含 4 4 4 行,每行一个整数,表示一张牌的值。

输出格式

对于每组牌,输出一行一个整数,表示用这 4 4 4 张牌能组合出的、不超过 24 24 24 的最大值。

样例输入

cpp 复制代码
3
3
3
3
3
1
1
1
1
12
5
13
1

样例输出

cpp 复制代码
24
4
21

提交链接

Twenty-four

思路分析

  1. 全排列枚举牌的顺序

    • 因为牌的排列顺序会影响运算结果(例如 3 3 3 - 5 5 5 和 5 5 5 - 3 3 3 不同)。
    • 4 4 4 张牌的所有排列数为 4 ! = 24 4! = 24 4!=24 种。
  2. 枚举运算符组合

    • 4 4 4 张牌有 3 3 3 个空隙,每个空隙可以放 + - * / 四种运算符。
    • 一共有 4 3 = 64 4^3 = 64 43=64 种运算符组合。
  3. 枚举括号(运算顺序)

    • 四个数、三个运算符,合法的运算顺序(括号摆放方式)有 5 5 5 种:
      1. ((a op1 b) op2 c) op3 d
      2. (a op1 (b op2 c)) op3 d
      3. a op1 ((b op2 c) op3 d)
      4. a op1 (b op2 (c op3 d))
      5. (a op1 b) op2 (c op3 d)
  4. 整数除法检查

    • 除法必须整除且不能除以零,运算时要判断。
  5. 更新最大值

    • 如果某个算式结果 ≤ 24 ≤ 24 ≤24 且合法,就用它来更新最大值。

  1. 全局变量与工具函数
  • vector<char> b{'+', '-', '*', '/'}

    • 存四种运算符,方便用下标枚举。
  • int mx

    • 记录当前测试用例中最大的不超过 24 24 24 的结果。
  • int cal(int x, char op, int y, bool &ok)

    • 执行一次二元运算 x op y
      ok 用来标记运算是否合法:
      • 如果是 / 运算,检查除数 y 是否为 0;
      • 检查是否整除 x % y == 0
    • 如果不满足条件,ok = false,表示该运算链无效。
  1. 读取数据
cpp 复制代码
int t;
cin >> t;
while (t--)
{
    vector<int> a(4);
    for (int &i : a) cin >> i;
}
  • 读取 t 组测试数据。
  • 每组数据读入 4 张牌的值。
  1. 枚举牌的顺序
cpp 复制代码
sort(a.begin(), a.end());
mx = 0;
do
{
    // ...
} while (next_permutation(a.begin(), a.end()));
  • sort 保证 next_permutation 从最小字典序开始枚举,确保不会漏掉排列。
  • next_permutation 会枚举 4 4 4 张牌的所有排列( 24 24 24 种)。
  • 每次排列会进入一次运算符与括号的枚举。
  1. 枚举运算符
cpp 复制代码
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,对应 + - * /
  • 一共 64 64 64 种运算符组合。
  1. 枚举括号方式并计算
  • 每种括号方式都要单独用一个 ok = true,防止上一次计算失败状态影响下一种括号结构。

(1) ((a op1 b) op2 c) op3 d

cpp 复制代码
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

cpp 复制代码
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)

cpp 复制代码
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))

cpp 复制代码
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)

cpp 复制代码
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 连接。

参考代码

cpp 复制代码
#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 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);
						//(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);
						// 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;
}
相关推荐
热爱生活的猴子1 分钟前
算法148. 排序链表
数据结构·算法·链表
孤独得猿9 分钟前
Redis类型之Hash
redis·算法·哈希算法
এ᭄画画的北北31 分钟前
力扣-5.最长回文子串
算法·leetcode
点云侠1 小时前
【2025最新版】PCL点云处理算法汇总(C++长期更新版)
c++·算法·计算机视觉·3d·可视化
Python智慧行囊2 小时前
排序算法总结
数据结构·算法
似水流年流不尽思念2 小时前
常见的排序算法有哪些?它们的平均时间复杂度是多少?
后端·算法
楽码3 小时前
端到端应用Hmac加密
服务器·后端·算法
Morriser莫3 小时前
图论Day2学习心得
算法·图论
zyd09153 小时前
代码随想录Day50:图论(图论理论、深度搜索理论、所有可达路径、广度搜索理论)
java·数据结构·算法·leetcode·图论