蓝桥杯2019省赛,编程题(C++)
给定 N 个加号、M 个减号以及 N+M+1 个整数A1,A2,⋅⋅⋅,AN+M+1,小明想知道在所有由这 N 个加号、M 个减号以及N+M+1 个整数凑出的合法的 后缀表达式中,结果最大的是哪一个?
请你输出这个最大的结果。
例如使用 1 2 3 + -,则 “2 3 + 1 -” 这个后缀表达式结果是 4,是最大的。
第一行包含两个整数 N,M。
第二行包含N+M+1 个整数 A1,A2,⋅⋅⋅,A{N+M+1}。
其中,0≤N,M≤10的5次方,−10的9次方≤Ai≤10的9次方。
输出一个整,代表答案。
输入
1 1
1 2 3
输出
4
题目看似简单,思考过程很难!这是我写这道题最大的感触!
按照题意的要求,我们需要在给定的一些加号和减号中,组成后缀表达式来求出这个表达式的最大值。我一开始用的是中序表达式写的,甚至提交了5,6遍错误答案之后还是没想明白错在哪里… 后来终于发现了后续表达式和中序表达式的区别:
后续表达式,是一种计算机非常容易识别的算术规则,因此广泛运用与计算机科学领域。
对于 a+b*c+(d * e + f)*g 这样一个中序表达式,转为后续表达式就是 : **abc +de * f +g +
那么从这个后续表达式转换为前面这样的一个中序表达式的过程主要通过两个栈来实现,一个是符号栈,一个是数字栈,首先程序顺序读入这样的一行后续表达式
1)a进数字栈
2)b进数字栈
3)c进数字栈
4)乘法 * 进入符号栈
5)符号栈 * 出栈,数字栈c,b出栈,相乘变为b*c再入数字栈
6)现在数字栈为a(b*c),然后加法 + 入符号栈
7)符号栈 + 出栈,数字(b*c),a出栈,相加变为a+(b * c)入数字栈栈
8)现在数字栈为(a+(b*c)),然后d入栈,e入栈
9)乘法 * 进入符号栈
10)符号栈 * 出栈,数字栈e,d出栈,相乘变为d*e 再入数字栈
11)现在数字栈为(a+(b*c))(d * e),然后f入栈
12)加法 + 进入符号栈
13)符号栈 + 出栈,数字(d*e),f出栈,相加变为(d * e) + f入数字栈栈
14)现在数字栈为(a+(b*c))((d * e)+ f)
15)g入数字栈
16)*入符号栈
17)符号栈 * 出栈,数字栈((d * e)+ f),g出栈,相乘变为((d * e)+ f)*g入栈
18)现在数字栈为(a+(b*c))(((d * e)+ f)*g),+入符号栈
19)符号栈 + 出栈,数字栈(a+(b*c)),(((d * e)+ f)*g)出栈,相加变为(a+(b *c))+(((d * e)+ f)*g)入数字栈
20)数字栈为((a+(b *c))+(((d * e)+ f)*g)),去掉不影响运算顺序的括号即为 a+b * c+(d * e + f)*g
通过以上例子,不难发现后续表达式的一个重要特点(这也是我按照中序表达式去算会结果出错的原因)那就是后续表达式可以在不借助()的情况下,去表示算术的优先级,比如前面这里例子中,中序表达式是有括号的,而转换到后续表达式,就不需要括号了,那么这个题目的潜在含义也就出来了,就是如果用中序表达式的思路来解决此题时,我们需要考虑括号的存在,也就是运算优先级的存在。
这一结论,也就说明了减法个数将会对于最大值有影响(因为有括号的存在)。
输入样例为:(三个加号,一个减号)
3 1
1 -2 3 -4 5
最大值为15,中序表达式为 5+1+3 - ((-2)+(-4))
输入样例为:(两个加号,两个减号)
2 2
1 -2 3 -4 5
最大值为15,中序表达式为5+3+1-(-2)-(-4)
输入样例为:(一个加号,三个减号)
1 3
1 -2 3 -4 5
最大值为15,中序表达式为5-((-2)+(-4)-1-3)
输入样例为:(四个减号)
0 4
1 -2 3 -4 5
最大值为15,中序表达式为5-((-2)-1-3)-(-4)
如果全是加号,直接累加就可以。
那么通过以上的分析,题目的思路就已经明朗了,首先要分不含减号和含有减号两种情况来处理,对于含有减号的情况,还要再细分:
全为正数,那么通过减号和括号的运用,最终可以使得表达式只损失一个最小值,比如
0 3
1 2 3 4
最大值为 8,中序表达式为4-(1-2-3)
全为负数,原理和全为正数一样,把所有的负数都转换成绝对值累加,然后再把绝对值最小的负数减去
0 3
-1 -2 -3 -4
最大值为8,中序表达式为(-1)-(-2)-(-3)-(-4)
负数和正数混合,这里就很神奇了,就像我前面举的那个例子一样,只要有大于等于一个减号存在,那么通过减号和括号的巧妙配合,可以让最大值变成所有项绝对值的累加,这里所得的结论就是这样的,具体的原理大家可以通过例子细细体会,毕竟难题有时候真的需要感觉和实践来领悟。
写到这里,所有的情况就都被考虑进来了。
#include
using namespace std;
long a[200005];
int main()
{
int n,m;
cin>>n>>m;
long long ans=0;
long count_neg=0; //统计负数的个数
long minest=1000000009;
for(int i=0;i<n+m+1;i++)
{
cin>>a[i];
ans+=a[i];
if(a[i]<0) count_neg++;
minest=min(minest,a[i]); //统计最小的数
}
if (m == 0) cout<<ans<<endl; //如果都是加号,那么直接输出结果
else
{
if(count_neg == 0) cout<<ans-minest-minest<<endl; //如果没有负数,那么减去最小的,即为答案,减2次因为累加的时候加过一次
else //既有负数,又可能有正数,既有加号,又有减号
{
if(count_neg==n+m+1) //全是负数
{
ans = 0; minest = 1000000009; //重新刷新一下结果
for (int i = 0; i < n + m + 1; ++i) //先全部按照正数累加
{
ans += abs(a[i]);
minest = min(minest, abs(a[i])); //统计绝对值最小的负数
}
cout<<ans-minest-minest<<endl; //减去绝对值最小的负数即可,减两次的原因和前面一样
}
else
{
ans = 0;
for (int i = 0; i < n + m + 1; ++i) ans += abs(a[i]); //正数和负数都有,全部用绝对值累加
cout<<ans<<endl;
}
}
}
return 0;
}
代码是我参考的一篇博主的代码:蓝桥杯2019年第十届真题-后缀表达式-题解(C++代码)-Dotcpp编程社区 改写后的,自己只是一个软工小白,对于难题确实也没啥好的思路,写这样的一篇博客,一个是想通过这个过程再对这个解题思路熟悉一下,另外就是想通过自己的理解再把这个题说明白点,因为自己在这道题的题解时感觉还是有些疑问在里面,如果有帮助到大家,不胜荣幸!