【题目链接】
ybt 1956:【11NOIP普及组】表达式的值
洛谷 P1310 [NOIP2011 普及组] 表达式的值
【题目考点】
- 表达式树
由带括号的中缀表达式构建表达式树
【解题思路】
思路1:构建表达式树,树形动规
-
题目中定义的 + + +为或运算, ∗ * ∗为与运算。
-
人为在运算符表达式末尾添加’)',作为表达式结束符。
-
中缀表达式构建表达式树的过程,和中缀表达式求值的过程类似。设结点栈(对应数字栈),运算符栈
-
符号优先级从低到高:右括号), 或运算+, 与运算*, 左括号(。
其中左括号比较特殊,左括号在入栈前,比所有运算符优先级高。左括号在栈顶时,比所有运算符优先级低。 -
运算符入栈条件:运算符栈栈空,或待入栈运算符优先级比栈顶运算符优先级高,或运算符栈栈顶是左扩号
-
生成运算符结点:结点栈出栈两个结点,运算符栈出栈一个运算符,分配一个新的结点,新结点的运算符为刚出栈的运算符,将两个结点作为新结点的左右孩子,将新结点在结点栈入栈。
-
遍历表达式,
- 遇到非括号运算符,即视为遍历到该运算符左侧的数字和该运算符。
- 遇到左括号,仅视为遇到运算符。
- 若遇到当前运算符的左侧还是右括号,则不算遍历到数字,否则算遍历到数字。
-
如果遍历到数字,则生成数字结点,结点入栈。
-
对于运算符
- 左括号直接入栈
- 如果待入栈的是右括号,栈顶是左括号,则二者抵消,左括号出栈。
- 如果满足入栈条件,则入栈。否则,不断生成运算符结点,直到满足入栈条件时,入栈。
-
最后结点栈栈顶元素即为表达式树的树根。
-
树形动规求解:
状态定义:dp[i][j]
:以i为根的子树所代表的表达式的值为j的情况数
设u的左孩子为l,右孩子为r- 若根结点u的运算符为或运算+
若结果为0,必须参与运算的两个数都为0,根据乘法原理,二者的情况数相乘可得结果为0的情况数。即:dp[u][0] = dp[l][0]*dp[r][0]
若结果为1,则参与运算的两个数可以是:0,1; 1,0; 1,1
dp[u][1] = dp[l][0]*dp[r][1]+dp[l][1]*dp[r][0]+dp[l][1]*dp[r][1]
- 若根结点运算符为与运算*,
若结果为0,两个参与运算的数要分别为0,0或0,1或1,0,
dp[u][0] = dp[l][0]*dp[r][1]+dp[l][1]*dp[r][0]+dp[l][0]*dp[r][0]
若结果为1,两个参与运算的数要分别为1,1
dp[u][1] = dp[l][1]*dp[r][1]
- 若根结点u的运算符为或运算+
-
求结果的过程中只用到加法与乘法,根据:
( a + b ) % m = ( a % m + b % m ) % m (a+b)\%m = (a\%m+b\%m)\%m (a+b)%m=(a%m+b%m)%m, ( a ∗ b ) % m = ( ( a % m ) ∗ ( b % m ) ) % m (a*b)\%m = ((a\%m)*(b\%m))\%m (a∗b)%m=((a%m)∗(b%m))%m。
每次运算后结果%10007,即可得到最终结果。
思路2:设结点,表示结果为0或1的情况数
思路层面还是要借助表达式树来理解。表达式树上每个结点都记录了以该结点为根的子树表示的表达式结果为0的情况数和结果为1的情况数。在构建表达式树的同时,将每个结点的这两种情况数求出来,最终得到根结点上结果为0的情况数,即为问题的解。
因此在写代码时,不需要真的去构造一个表达式树。只需要模仿中缀表达式求值的过程进行运算即可,而这里参与运算的不是数字,而是包含“结果为0的情况数和结果为1的情况数”的结点。
【题解代码】
解法1:构建表达式树,树形动规
#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define M 10007
struct Node
{
char c;
int left, right;
};
Node node[2*N];
int p, dp[2*N][2];//dp[i][0/1]:以i为根的子树所代表的表达式的值为0/1的情况数
stack<int> nStk;//存的是结点地址
stack<char> cStk;
int pri(char c)
{
switch(c)
{
case '(': return 5;
case '*': return 4;
case '+': return 3;
case ')': return 2;
}
}
void dfs(int u)
{
if(node[u].left == 0 && node[u].right == 0)
{
dp[u][0] = dp[u][1] = 1;
return;
}
int l = node[u].left, r = node[u].right;
dfs(l);
dfs(r);
if(node[u].c == '+')
{
dp[u][0] = dp[l][0]*dp[r][0]%M;
dp[u][1] = (dp[l][0]*dp[r][1]+dp[l][1]*dp[r][0]+dp[l][1]*dp[r][1])%M;
}
else if(node[u].c == '*')
{
dp[u][0] = (dp[l][0]*dp[r][1]+dp[l][1]*dp[r][0]+dp[l][0]*dp[r][0])%M;
dp[u][1] = dp[l][1]*dp[r][1]%M;
}
}
int main()
{
int len, lp, rp;
string s;
cin >> len >> s;
s += ')';
for(int i = 0; i <= len; ++i)
{
if(s[i] != '(' && (i == 0 || s[i-1] != ')'))
{//如果s[i]前是是一个数字
int np = ++p;
nStk.push(np);
}
while(!(cStk.empty() || pri(s[i]) > pri(cStk.top()) || cStk.top() == '('))
{
int np = ++p;
node[np].c = cStk.top(); cStk.pop();
node[np].right = nStk.top(); nStk.pop();
node[np].left = nStk.top(); nStk.pop();
nStk.push(np);
}
if(cStk.empty() == false && cStk.top() == '(' && s[i] == ')')
cStk.pop();
else
cStk.push(s[i]);
}
int root = nStk.top();
dfs(root);
cout << dp[root][0];
return 0;
}
解法2:设结点表示结果为0或1的情况数
#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define M 10007
struct Node
{
int n0, n1;//n0:结果为0的情况数 n1:结果为1的情况数
};
Node node[2*N];
int p;
stack<int> nStk;//存的是结点地址
stack<char> cStk;
int pri(char c)
{
switch(c)
{
case '(': return 5;
case '*': return 4;
case '+': return 3;
case ')': return 2;
}
}
int main()
{
int len, lp, rp;
string s;
cin >> len >> s;
s += ')';
for(int i = 0; i <= len; ++i)
{
if(s[i] != '(' && (i == 0 || s[i-1] != ')'))
{//如果s[i]前是是一个数字
int np = ++p;
node[np].n0 = node[np].n1 = 1;
nStk.push(np);
}
while(!(cStk.empty() || pri(s[i]) > pri(cStk.top()) || cStk.top() == '('))
{
int np = ++p;
char c = cStk.top(); cStk.pop();
rp = nStk.top(); nStk.pop();
lp = nStk.top(); nStk.pop();
int l1 = node[lp].n1, l0 = node[lp].n0, r1 = node[rp].n1, r0 = node[rp].n0;
if(c == '+')
{
node[np].n0 = (l0*r0)%M;
node[np].n1 = (l0*r1+l1*r0+l1*r1)%M;
}
else if(c == '*')
{
node[np].n0 = (l1*r0+l0*r1+l0*r0)%M;
node[np].n1 = (l1*r1)%M;
}
nStk.push(np);
}
if(cStk.empty() == false && cStk.top() == '(' && s[i] == ')')
cStk.pop();
else
cStk.push(s[i]);
}
cout << node[nStk.top()].n0;
return 0;
}