题目:
表达式求值
题意:
给定一个长度不超过$10^5$的表达式,请你求出这个表达式的值。
保证数字计算过程中直接得到的数字不会为负数,且只有+
,-
,*
,/
,(
,)
这$6$种字符。
数据范围:$|s| \leq 10^5$
题解:
这里将加减称为add
,乘除称为mul
。
因为减法可以转换为一个数加上一个负数,除法的分母模意义下可以转换为逆元。
考虑我们通常对一个不带括号的中缀表达式进行求值,大概如下四种情况:
- 前
add
后add
:a+b+d
,那么我们可以先计算得到a+b=c
,然后再计算c+d
即可。 - 前
mul
后mul
:a*b*d
,那么我们可以先计算得到a*b=c
,然后再计算c*d
即可。 - 前
mul
后add
:a*b+d
,那么我们可以先计算得到a*b=c
,然后再计算c+d
即可。 - 前
add
后mul
:a+b*d
,此时由于乘号的优先级更高,我们需要先求得乘法的结果b*d=c
,再计算a+c
。
可以看到前三种情况,当前一个计算符号
优先级不比后一个计算符号
优先级低时,其可以直接计算,然后将计算结果代入之后的表达式进行计算。而当前一个计算符号
优先级比后一个计算符号
优先级低时,其只能等待后一个计算符号
的计算结果,然后再进行计算。
综上我们可以考虑维护一个符号优先级严格单调递增的单调栈,可以想到这个栈并不会用多少的内存,因为栈内最多只会有两个元素,栈底为加号或减号,栈顶为乘号或除号。
接下来考虑带括号的计算。
括号是提升一个表达式优先级的符号,但并不会进行实际的加减乘除计算。
对应大概三种中缀表达式:
-
前
add/mul
后()
:a*(b+c)
等…
这种表达式的计算与 前add
后mul
是一致的,只是我们需要先计算括号内表达式的值。所以在任何情况下(
一定是可以入栈的,栈内的计算与普通的计算相同。 -
前
(
后)
:(a+b)
等…
其实这就是对括号内的表达式进行求值,当我们遇到一个)
时,表示这个)
和其匹配的(
之间的表达式为这个括号对应的括号表达式,此时表示这个括号表示式需要完成计算得到计算结果了。
需要注意的是,由于在左括号在任何情况下都可以入栈,所以当遇到((a+b))
或(a+(b*c+d))
这种情况时,入栈后的栈将不再满足严格单调递增的栈的属性,所以这里的单调栈是严格单调递增的性质将由于左括号的存在不再完整,而是每两个左括号作为一个分隔符的计算符号序列都将单独满足严格单调递增,当然,这些符号序列的长度也不会大于$2$。
至于(
入栈后,任何的表达式符号都可以入栈,表示其优先级已经降为最低。
我们可以在给定表达式的首添加一个(
,尾添加一个)
,保证整体描述的完整性的同时也会使得计算无需单独解决。具体如a+b
,你需要在栈构建完毕后再对栈单独的操作,在首尾添加一个括号后将使得实现更为优美。 -
前
()
后add/mul
:(a+b)*c
,这种同前mul
后add
的计算,不再赘述。
本题已说明不会存在负数,但具体负数和减号这两种还是有办法可以区分的。
考虑什么时候-
将作为减号:
- 当
-
的前面为)
时 - 当
-
的前面为一个数时
这两者都表示该-
前面的表达式可以直接计算为结果代入之后表达式的计算,这是由减号的优先级在计算符号中是最低来决定的。
代码:
本份代码支持$2^{31}-1$以内的带负数的正负加减乘除和括号计算。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
char op[N]; int topo;
int num[N]; int topn;
char s[N];
int n;
int cal(char ch) {
int b = num[topn--];
int a = num[topn--];
if(ch == '+') return a + b;
else if(ch == '-') return a - b;
else if(ch == '*') return a * b;
return a / b;
}
int level(char ch) {
if(ch == '(') return 0;
else if(ch == '+' || ch == '-') return 1;
return 2;
}
bool check_f(int i) {
if(i > 2 && (isdigit(s[i - 1]) || s[i - 1] == ')')) return false;
return true;
}
int main()
{
scanf("%s", s + 2);
n = strlen(s + 2) + 2;
s[1] = '(', s[n] = ')';
for(int i = 1; i <= n; ++i) {
if(isdigit(s[i]) || (s[i] == '-' && check_f(i))) {
int c = 0, f = 1;
if(s[i] == '-') f = -1, ++i;
while(i <= n && isdigit(s[i])) c = c * 10 + s[i] - '0', ++i;
num[++topn] = c * f;
--i;
}
else {
if(s[i] == '(') op[++topo] = s[i];
else if(s[i] == ')') {
while(topo > 0 && op[topo] != '(') {
char ch = op[topo--];
int c = cal(ch);
num[++topn] = c;
}
--topo;
}
else {
while(topo > 0 && op[topo] != '(' && level(s[i]) <= level(op[topo])) {
char ch = op[topo--];
int c = cal(ch);
num[++topn] = c;
}
op[++topo] = s[i];
}
}
}
printf("%d\n", num[topn]);
return 0;
}