【递归完全搜索】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;
}
相关推荐
obroccolio6 分钟前
【C++题解】关联容器
开发语言·数据结构·c++·算法
沐怡旸10 分钟前
【算法--链表】25.K个一组翻转链表--通俗讲解
算法·面试
快去睡觉~14 分钟前
力扣190:颠倒二进制位
数据结构·算法·leetcode
惯导马工25 分钟前
【论文导读】CTIN: Robust Contextual Transformer Network for Inertial Navigation
算法
骑驴看星星a1 小时前
皮尔逊相关(Pearson)和斯皮尔曼相关(Spearman)显著性检验
算法·数学建模·回归·线性回归
yingxiao8881 小时前
日本移动应用市场营销分析:娱乐和金融应用增长强劲,游戏类广告支出最高!
游戏·动画·业界资讯
MT_1251 小时前
大小端存储的理解与判断方法
数据结构·算法
玩镜的码农小师兄2 小时前
[从零开始面试算法] (11/100) LeetCode 226. 反转二叉树:递归的“镜像”魔法
c++·算法·leetcode·面试·递归·hot100
喜欢吃豆3 小时前
LangGraph 深度解析(三):构建可观测、交互式 AI 智能体的流式架构权威指南
人工智能·python·算法·架构·大模型
玉木子5 小时前
机器学习(六)朴素贝叶斯分类
开发语言·人工智能·python·算法·机器学习·分类