问题描述:
24点游戏是一个非常有意思的游戏,很流行,玩法很简单:给你4张牌,每张牌上有数字(其中A代表1,J代表11,Q代表12,K代表13),你可以利用数学中的加、减、乘、除以及括号想办法得到24,例如:
((A*K)-J)*Q等价于((1*13)-11)*12=24
加减乘不用多说了,但除法必须满足能整除才能除!这样有一些是得不到24点的,所以这里只要求求出不超过24的最大值。
输入格式:
输入第一行N(1<=N<=5)表示有N组测试数据。每组测试数据输入4行,每行一个整数(1到13)表示牌值。
输出格式:
每组测试数据输出一个整数,表示所能得到的最大的不超过24的值。
样例输入:
3
3
3
3
3
1
1
1
1
12
5
13
1样例输出:
24
4
21
前言:
参考解法:
蓝桥杯 算法训练 24点_陆小路-1的博客-CSDN博客
原博客讲得很详细,评论区也有不少问题解答。
(仅需解法的 同学/读者 直接跳转 即可)
这里我还是只讲一下我的解题过程(主要是错误的原因)。
临近省赛,这篇文章就再小小总结一下目前做过的《算法训练》题目。
(删了一大段感慨的话)
笔者,也是要去刷一刷官网的真题才行……
开始:
题做到这里,相信大家已经对一些算法(dp,dfs)有了一定的了解。
已经做过的题目:
《印章》:dp(动态规划),最终结果由上一步结果推导出。
《拿金币》:同上。
《无聊的逗》:经典的dfs算法。
《礼物》:找规律。
《跳马》:dfs(深度优先搜索), bfs(宽度优先搜索) 都能解。
《数的潜能》:找规律。
《娜神平衡》:dfs + dp?
《粘木棍》:dfs
《车的放置》:dfs
《24点》:dfs
其中,《印章》《拿金币》《礼物》《数的潜能》就不讲了,随机应变吧。
笔者浅浅总结一下这些题所用dfs的区别。
《无聊的逗》,经典的dfs,利用每层递归的三种操作,遍历出所有的情况。但仅限于对当前"对象"进行操作。
《跳马》,经典的dfs,利用每层递归的两种操作,遍历出所有的情况。但也仅限于对当前"对象"进行操作。
《娜神平衡》,同为dfs操作,但它却可以实现:对一些对象进行选择(每层内部都有一个for循环遍历所有对象)。不难发现,其依据是用int或bool数组存放对象的状态。
《粘木棍》,……
《车的放置》,……
《24点》,而本题的dfs操作又有不同,它是每层循环都挑出两个数,对齐进行(加减乘除)。
笔者水平有限,对这些dfs的总结为:
橙色dfs的作用:对某个对象进行不同的操作,适用于穷举"按顺序"的所有情况。
例:给定ABCDEFG……不同的物品,每个物品都可以拿或不拿,请穷举所有情况。
黄色dfs的作用:对某个对象进行不同的操作,适用于穷举"不按顺序"的所有情况。
例:给定ABCDEFG……不同的物品,请穷举所有组合(ABCDEGF……)。
绿色dfs的作用:对两个对象进行不同的操作,适用于穷举"两两配对?"的所有情况。
其实跟黄色dfs差不多,,,
接下来看题,其本质就是问:对4个数进行加减乘除的操作,怎么得到小于等于24最大的数?
一开始我是延续上几题《娜神平衡》的dfs想法,但提交程序后正确率最高只有60%。
上网查了以后发现存在优先级和分配律的问题,
按自己的方式,笔者写的检查错误的代码如下:
#include
using namespace std;
//分组数量
int N = 0;
//数据:最多5组,每组4个
int Date[5][4] = { 0 };
//标记数据状态:false:未被使用;true:已使用
int Date_index[5][4] = { 0 };
int result[6] = { 0 };
//检查数据是否"全部"都已使用 - 作为边界条件
bool func01(int row)
{
for (int i = 0; i < 4; i++)
{
if (!Date_index[row][i])
return false;
}
return true;
}
//将数字转"字符"返回
string func02(int sum)
{
string s = "";
do
{
s += '0' + sum % 10;
sum /= 10;
} while (sum > 0);
reverse(s.begin(), s.end());
return s;
}
string S;
void func(const int row, int sum = 0, bool bol = false, string s = "", int x = 0)
{
x++;
//若结果已为最大值,无需继续
if (result[row] == 24)
return;
//该row组数据全部用上 - (更新)结束
if (func01(row))
{
s += " = " + func02(sum);
if (result[row] < sum && sum <= 24)
{
result[row] = sum;
S = s;
}
return;
}
//第一次调用时,填充任一数字作为第一位(防止存在0 * 0导致莫名去除了一些数据)
if (!bol)
{
for (int i = 0; i < 4; i++)
{
sum = Date[row][i];
Date_index[row][i] = x;//标记已使用
s = func02(Date[row][i]);
func(row, sum, true, s, x);
Date_index[row][i] = 0;//回溯
}
}
//第二/三/四次调用
else
{
for (int i = 0; i < 4; i++)
{
if (!Date_index[row][i])
{
Date_index[row][i] = x;//标记已使用
string temp;
int x1 = sum + Date[row][i];//加
temp = s + " + " + func02(Date[row][i]);
func(row, x1, true, temp, x);
int x2 = sum - Date[row][i];//减
temp = "|" + s + " - " + func02(Date[row][i]) + "|";
x2 = x2 > 0 ? x2 : -x2;
func(row, x2, true, temp, x);
int x3 = sum * Date[row][i];//乘
temp = "(" + s + ")" + " * " + func02(Date[row][i]);
func(row, x3, true, temp, x);
if (Date[row][i] != 0 && sum % Date[row][i] == 0)
{
temp = "(" + s + ")" + " / " + func02(Date[row][i]);
func(row, sum / Date[row][i], true, temp, x);//除
}
else if (sum != 0 && Date[row][i] % sum == 0)
{
temp = func02(Date[row][i]) + " / " + "(" + s + ")";
func(row, Date[row][i] / sum, true, temp, x);//除
}
Date_index[row][i] = 0;//回溯
}
}
}
}
int n = 1;//n组测试数据
int nums[4];//4个数字
int ans;//不超过24的最大值
//函数功能: 在有n个数的数组nums中 寻找最大的不超过24的值。
void f(int nums[], int n) {
//只有1个数时,比较最大值
if (n == 1) {
//如果该数不大于24,更新最大值
if (nums[n - 1] <= 24) {
ans = ans < nums[n - 1] ? nums[n - 1] : ans;
}
return;
}
//遍历两个数的组合
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
int a = nums[i], b = nums[j];
nums[j] = a + b; //加法和乘法具有交换律
nums[i] = nums[n - 1];
f(nums, n - 1);
nums[j] = a * b;
nums[i] = nums[n - 1];
f(nums, n - 1);
nums[j] = a - b; //减法和除法不具有交换律
nums[i] = nums[n - 1];
f(nums, n - 1);
nums[j] = b - a;
nums[i] = nums[n - 1];
f(nums, n - 1);
if (b != 0 && a % b == 0) { //除数不能为0
nums[j] = a / b;
nums[i] = nums[n - 1];
f(nums, n - 1);
}
if (a != 0 && b % a == 0) {
nums[j] = b / a;
nums[i] = nums[n - 1];
f(nums, n - 1);
}
nums[i] = a; //恢复现场
nums[j] = b;
}
}
}
int main()
{
输入数据
//cin >> N;
//for (int i = 0; i < N; i++)
// for (int j = 0; j < 4; j++)
// {
// cin >> Date[i][j];
// }
对每层进行运算
//for (int i = 0; i < N; i++)
// func(i);
输出结果
//for (int i = 0; i < N; i++)
// cout
// << S << endl
// //<< result[i] << endl
// ;
int cnt = 0;
for (int a = 1; a <= 13; a++)
{
for (int b = a; b <= 13; b++)
{
for (int c = b; c <= 13; c++)
{
for (int d = c; d <= 13; d++)
{
Date[0][0] = a;
Date[0][1] = b;
Date[0][2] = c;
Date[0][3] = d;
func(0);
cout << S << endl;
ans = 0;
nums[0] = a;
nums[1] = b;
nums[2] = c;
nums[3] = d;
f(nums, 4);
cout << " -> " << ans << endl;
if (ans != result[0])
system("pause>nul");
S = "";
result[0] = 0;
cnt++;
}
}
}
}
cout << cnt << endl;
return 0;
}
部分运行截图如下:
每次都会在 = 结果和 -> 结果 不同的时候暂停。
肉眼可得:若按最朴素的黄色dfs,对:1 1 1 9 得到的结果只有(1+1)*9+1=19最大。
但我们仍易得出:最大应该是(1 + 1) * (1 + 9) = 20,
可是原先的dfs思路是无法做到:将前后两个各自相加的。只能以(之前得到的数)一个数为基准,然后对其不断 加减乘除……
所以又需"升级"一下dfs解法。
具体解法看上面提到的博客,我就不详写了。
结束:
最近实在比较忙,还得准备省赛,抽空写文章小小总结一下dfs,分享 + 加深 自己的理解。
……