题解 P1236 【算24点】
这题的基本思路很简单,暴力。
对于四个数,枚举所有可能的式子,然后看结果是不是24。
但是式子怎么不重不漏地枚举呢?
有一个神奇的东西叫后缀表达式(又称逆波兰表达式)
引用一下百科上的定义:
后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
譬如,对于样例,一种可行的解是(2+1)*7+3,转换成后缀表达式就是
2 1 + 7 * 3 +
后缀表达式的计算的话是用一个栈,遇到数字就push,遇到运算符就pop栈顶前两个数进行运算并将结果入栈
算2 1 + 7 * 3 +,先2,1入栈,遇到加号,弹2,1,入3(即2+1的结果),然后入7,遇到乘号,弹3,7,入21(即3×7)的结果,然后入3,遇到加号,弹3,21,最后入24(3+21的结果),扫到最后栈里只剩一个元素,就是结果。
使用后缀表达式的好处是运算方便,开个栈扫一遍就出答案,如果是处理我们平常的那种的式子(中缀表达式),还要考虑括号优先级,而且用后缀表达式枚举起来也方便。
有四个操作数,假设是a,b,c,d,假设在后缀表达式中顺序已知,那么我们就可以在4个数中间和四个数的后面插入符号构成后缀表达式
a b c d
上面画下划线的地方可能有+-*/,也可能没有运算符。
6个位置,每个位置有5种状态(不填运算符或填+-*/),我们只要枚举所有可能的符号填写方式然后再算出表达式结果然后判断是不是24就万事大吉了。
我们可以使用6位五进制数表示可能的符号填写方式,五进制下第i位表示从左到右第i位可以填符号的地方填的内容,0对应不填,1对应+,2对-,3对*,4对/。
比如,5进制下120300就表示
a b + c - (空) d * (空) (空)
我们可以从5进制下000000枚举到5进下444444,然后再计算搞出来的后缀表达式,得24就出解,没有24就重新排列4个操作数,如果所有可能方案都不行,就是无解。
一些(细节?):
一、重新排列操作数:
c++ STL中有一个函数叫next_permutation,在algorithm包中。,使用它可以获得下一个排列。
返回值bool,参数:(数组起始地址,数组终止地址的下一个)
下一个排列指:
对于序列X,当且仅当序列Y满足:
Y的字典序比X大,且不存在字典序比X大而比Y小的序列
重排后的结果会存在你给它(指函数)的数组中。
最好先把输入升序排一下。
二、输出解,只要再算一遍后缀表达式边算边输出就好了。
三、操作数一定先输出大的后输出小的
四、中间过程不能出现负数或0或小数
五、可能还有一些问题没提到,有疑问看代码
最后ac代码:
#include<cstdio>
#include<stack>
#include<algorithm>
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int nums[5],oper[8],fl,pos[]={0,1,1,2,1,2,2,1,2,2,2};
int calc(int sets)
{
int opcnt=0;
//提取符号 ↓
for(int i=6;i>=1;i--)
{
oper[i]=sets%5;
sets/=5;
opcnt+=(oper[i]>0);
}
if(opcnt!=3)return -1;//4个操作数只能有3个运算符
//-----计算构造出的后缀表达式------↓
std::stack<int>s;
for(int i=1,j=1,k=1;i<=10;i++)
{
if(pos[i]==1)s.push(nums[j++]);//如果当前位置是数字,入栈
else
{
if(oper[k]!=0)//如果当前位置有符号,运算,没符号就跳过
{
int a,b;
if(!s.empty())a=s.top();//防止类似ab+c-/d的情况出现
else return -1;
s.pop();
if(!s.empty())b=s.top();
else return -1;
s.pop();
switch(oper[k])
{
case 1:
s.push(a+b);
break;
case 2:
if(a-b<=0)return -1;//不能有非正数
s.push(a-b);
break;
case 3:
s.push(a*b);
break;
case 4:
if(b==0||a%b!=0)return -1;//不能除0,不能有小数
s.push(a/b);
break;
}
}
k++;
}
}return s.size()==1?s.top():-1;
}
int work()
{
for(int i=1;i<=4;i++)scanf("%d",nums+i);
std::sort(nums+1,nums+5);
recnt:
for(int i=0;i<15625;i++)//枚举运算符填写情况 【6位5进制数最多表示15624】
if(fl=(calc(i)==24))break;//calc:计算构造出的后缀表达式结果
if(!fl)
{
if(!std::next_permutation(nums+1,nums+5))return printf("No answer!\n")&0;
else goto recnt;
}
//-----输出答案↓
std::stack<int>s;
for(int i=1,j=1,k=1;i<=10;i++)
{
if(pos[i]==1)s.push(nums[j++]);
else
{
if(oper[k]!=0)
{
int a=s.top();
s.pop();
int b=s.top();
s.pop();
switch(oper[k])
{
case 1:
s.push(a+b);
printf("%d+%d=%d\n",max(a,b),min(a,b),a+b);
break;
case 2:
s.push(a-b);
printf("%d-%d=%d\n",a,b,a-b);
break;
case 3:
s.push(a*b);
printf("%d*%d=%d\n",max(a,b),min(a,b),a*b);
break;
case 4:
s.push(a/b);
printf("%d/%d=%d\n",a,b,a/b);
break;
}
}
k++;
}
}
return 0;
}
int main()
{
return work();
}