【题目链接】

ybt 1956:【11NOIP普及组】表达式的值
洛谷 P1310 [NOIP2011 普及组] 表达式的值

【题目考点】

  1. 表达式树
    由带括号的中缀表达式构建表达式树

【解题思路】

思路1:构建表达式树

  • 题目中定义的+++为或运算,∗*∗为与运算。

  • 人为在运算符表达式末尾添加’#’,作为表达式结束符。

  • 中缀表达式构建表达式树的过程,和中缀表达式求值的过程类似。设结点栈(对应数字栈),运算符栈

  • 符号优先级从低到高:结束符#, 右括号), 或运算+, 与运算*, 左括号(。
    其中左括号比较特殊,左括号在入栈前,比所有运算符优先级高。左括号在栈顶时,比所有运算符优先级低。

  • 运算符入栈条件:待入栈运算符优先级比栈顶运算符优先级高,或运算符栈栈空,或运算符栈栈顶是左扩号

  • 生成运算符结点:结点栈出栈两个结点,运算符栈出栈一个运算符,分配一个新的结点,新结点的运算符为刚出栈的运算符,将两个结点作为新结点的左右孩子,将新结点在结点栈入栈。

  • 遍历表达式,

    • 遇到非括号运算符,即视为遍历到该运算符左侧的数字和该运算符。
    • 遇到左括号,仅视为遇到运算符。
    • 若遇到当前运算符的左侧还是右括号,则不算遍历到数字,否则算遍历到数字。
  • 如果遍历到数字,则生成数字结点,结点入栈。

  • 对于运算符

    • 左括号直接入栈
    • 如果待入栈的是右括号,栈顶是左括号,则二者抵消,左括号出栈。
    • 如果满足入栈条件,则入栈。否则,不断生成运算符结点,直到满足入栈条件时,入栈。
  • 最后结点栈栈顶元素即为表达式树的树根。

  • 递归求解:最后结果为0时运算数的情况数:

    • 若根结点的运算符为或运算+,则要使最后结果为0,必须参与运算的两个数都为0。求出左子树结果为0的情况数与右子树结果为0的情况数,二者相乘,即为最后结果为0的情况数。
    • 若根结点运算符为与运算*,则要使最后结果为0,两个参与运算的数要分别为0,0或0,1或1,0,
      最后结果为0的情况数 = 左子树结果为0的情况数*右子树结果为0的情况数 + 左子树结果为0的情况数*右子树结果为1的情况数 + 左子树结果为1的情况数*右子树结果为0的情况数。
    • 如果该结点是叶子结点,结果为0的情况数为1,结果为1的情况数为1。
      过程中用数组记忆中间结果,减少重复遍历
  • 求结果的过程中只用到加法与乘法,根据:
    (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
typedef struct Node
{char c;//结点的运算符,可以为:'+'(或运算),*(与运算),'n'(该结点表示数字)。int left, right;
}Node;
Node node[2*N];//结点池,叶子结点最多N个,整个二叉树结点数不会超过2N个。
int n_i;//结点池待分配位置。
char s[N];//保存输入的字符串
int nodeStk[2*N], ntop, ctop;//nodeStk结点栈 ntop:结点栈栈顶地址, ctop:运算符栈栈顶地址
char calcStk[2*N];//运算符栈
int sitNum[2*N][2];//sitNum[root][num]:记录根结点地址为root的表达式树的值为num时的运算数情况数。int pri[128];//pri[运算符]的值为该运算符的优先级
int initPri()
{pri['#'] = 0;pri[')'] = 1;pri['+'] = 2;pri['*'] = 3;pri['('] = 4;
}bool isPri(char a, char b)//运算符a的优先级是否比运算符b高
{return pri[a] > pri[b];
}//获取以root为根的表达式树结果为res时,运算数的所有情况数
int getSitNum(int root, int res)
{if(sitNum[root][res] > 0)//如果已经求出过结果return sitNum[root][res];if(node[root].c == 'n'){sitNum[root][res] = 1;return 1;}else{int l = node[root].left, r = node[root].right;if(node[root].c == '+')//或运算{if(res == 1)sitNum[root][res] = getSitNum(l, 1) * getSitNum(r, 0) + getSitNum(l, 0) * getSitNum(r, 1) + getSitNum(l, 1) * getSitNum(r, 1);elsesitNum[root][res] = getSitNum(l, 0) * getSitNum(r, 0);}else if(node[root].c == '*')//与运算{if(res == 1)sitNum[root][res] = getSitNum(l, 1) * getSitNum(r, 1);elsesitNum[root][res] = getSitNum(l, 1) * getSitNum(r, 0) + getSitNum(l, 0) * getSitNum(r, 1) + getSitNum(l, 0) * getSitNum(r, 0);}sitNum[root][res] %= 10007;return sitNum[root][res];}
}int main()
{initPri();int len, np;scanf("%d", &len);scanf("%s", s);s[len] = '#';//字符串有字符的位置为0 ~ len-1,在len位置添加结束符for(int i = 0; i <= len; ++i){if(s[i] == '(')calcStk[++ctop] = s[i];//入栈else{if(i == 0 || s[i - 1] != ')')//如果前一个运算符不是右括号,生成数字结点,即为叶子结点{np = n_i++;//分配新结点,地址是npnode[np].c = 'n';nodeStk[++ntop] = np;//新结点入栈}while(!(isPri(s[i], calcStk[ctop]) || ctop == 0 || calcStk[ctop] == '('))//不断生成运算符结点直到满足运算符入栈条件{np = n_i++;//分配新结点,地址是npnode[np].c = calcStk[ctop--];node[np].left = nodeStk[ntop--];node[np].right = nodeStk[ntop--];nodeStk[++ntop] = np;//新结点入栈}if(s[i] == ')')ctop--;elsecalcStk[++ctop] = s[i];//运算符入栈}}int root = nodeStk[ntop];printf("%d", getSitNum(root, 0));return 0;
}

解法2:设结点表示结果为0或1的情况数

#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define M 10007
typedef struct Node
{int n0, n1;//n0:结果为0的情况数 n1:结果为1的情况数
}Node;
Node node[2*N];//结点池
int n_i;//结点池待分配位置。
char s[N];//保存输入的字符串
int nStk[2*N], ntop, ctop;//nodeStk结点栈 ntop:结点栈栈顶地址, ctop:运算符栈栈顶地址
char cStk[2*N];//运算符栈int pri[128];//pri[运算符]的值为该运算符的优先级
int initPri()
{pri['#'] = 0;pri[')'] = 1;pri['+'] = 2;pri['*'] = 3;pri['('] = 4;
}
bool isPri(char a, char b)//运算符a的优先级是否比运算符b高
{return pri[a] > pri[b];
}
int main()
{initPri();int len, np, nl, nr;char c;scanf("%d", &len);scanf("%s", s);s[len] = '#';for(int i = 0; i <= len; ++i){if(s[i] == '(')cStk[++ctop] = s[i];else{if(i == 0 || s[i - 1] != ')'){//数字结点入栈np = n_i++;node[np].n0 = 1;node[np].n1 = 1;nStk[++ntop] = np;}while(!(isPri(s[i], cStk[ctop]) || ctop == 0 || cStk[ctop] == '('))//不断生成结点直到满足运算符入栈条件{//出栈运算符和结点,生成新结点np = n_i++;//分配新结点,地址是npnl = nStk[ntop--];//出栈,获得左侧运算数nr = nStk[ntop--];//出栈,获得右侧运算数c = cStk[ctop--];//运算符if(c == '+')//或运算{node[np].n0 = (node[nl].n0 * node[nr].n0) % M;node[np].n1 = (node[nl].n0 * node[nr].n1 + node[nl].n1 * node[nr].n0 + node[nl].n1 * node[nr].n1) % M;}else if(c == '*')//与运算{node[np].n0 = (node[nl].n0 * node[nr].n1 + node[nl].n1 * node[nr].n0 + node[nl].n0 * node[nr].n0) % M;node[np].n1 = (node[nl].n1 * node[nr].n1) % M;}nStk[++ntop] = np;//新结点入栈}if(s[i] == ')')ctop--;//左右括号抵消elsecStk[++ctop] = s[i];//运算符入栈}}cout<<node[nStk[ntop]].n0;//输出栈顶结点的结果为0的情况。return 0;
}

同样解法,用STL

#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define M 10007
typedef struct Node
{int n0, n1;//n0:结果为0的情况数 n1:结果为1的情况数
}Node;
Node node[2*N];
int p = 1;
stack<int> nStk;//存的是结点地址
stack<char> cStk;
int pri[128];
void initPri()
{pri['('] = 5; pri['*'] = 4; pri['+'] = 3; pri[')'] = 2;;
}
int main()
{initPri();int len, np, lp, rp;char c;string s;cin>>len;cin>>s;s += ')';for(int i = 0; i <= len; ++i){if(s[i] != '(' && (i == 0 || s[i-1] != ')')){np = p++;node[np].n0 = node[np].n1 = 1;nStk.push(np);}while(!(cStk.empty() || pri[s[i]] > pri[cStk.top()] || cStk.top() == '(')){np = p++;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();elsecStk.push(s[i]);}cout<<node[nStk.top()].n0;return 0;
}

信息学奥赛一本通 1956:【11NOIP普及组】表达式的值 | 洛谷 P1310 [NOIP2011 普及组] 表达式的值相关推荐

  1. 信息学奥赛一本通 1089:数字反转 | 1953:【11NOIP普及组】数字反转 | OpenJudge NOI 1.5 29 | 洛谷 P1307 [NOIP2011 普及组] 数字反转

    [题目链接] ybt 1089:数字反转 ybt 1953:[11NOIP普及组]数字反转 OpenJudge NOI 1.5 29:数字反转 洛谷 P1307 [NOIP2011 普及组] 数字反转 ...

  2. 信息学奥赛一本通 1962:【13NOIP普及组】表达式求值 | 洛谷 P1981 [NOIP2013 普及组] 表达式求值

    [题目链接] ybt 1962:[13NOIP普及组]表达式求值 洛谷 P1981 [NOIP2013 普及组] 表达式求值 [题目考点] 栈 中缀表达式转后缀表达式,后缀表达式求值 中缀表达式求值 ...

  3. 信息学奥赛一本通 1400:统计单词数 | 1954:【11NOIP普及组】统计单词数 | OpenJudge NOI 1.12 05 | 洛谷 P1308 [NOIP2011 普及组] 统计单词数

    [题目链接] ybt 1400:统计单词数 ybt 1954:[11NOIP普及组]统计单词数 OpenJudge NOI 1.12 05:统计单词数 洛谷 P1308 [NOIP2011 普及组] ...

  4. 信息学奥赛一本通 1955:【11NOIP普及组】瑞士轮 | OpenJudge NOI 4.1 4363:瑞士轮 | 洛谷 P1309 [NOIP2011 普及组] 瑞士轮

    [题目链接] ybt 1955:[11NOIP普及组]瑞士轮 OpenJudge NOI 4.1 4363:瑞士轮 洛谷 P1309 [NOIP2011 普及组] 瑞士轮 [题目考点] 1. 归并排序 ...

  5. 信息学奥赛一本通 1415:【17NOIP普及组】图书管理员 | 洛谷 P3955 [NOIP2017 普及组] 图书管理员

    [题目链接] ybt 1415:[17NOIP普及组]图书管理员 洛谷 P3955 [NOIP2017 普及组] 图书管理员 [题目考点] 1. 枚举 2. 二分 [解题思路] 解法1:枚举 对于每个 ...

  6. 信息学奥赛一本通 1314:【例3.6】过河卒(Noip2002) | 1921:【02NOIP普及组】过河卒 | 洛谷 P1002 [NOIP2002 普及组] 过河卒

    [题目链接] ybt 1314:[例3.6]过河卒(Noip2002) ybt 1921:[02NOIP普及组]过河卒 洛谷 P1002 [NOIP2002 普及组] 过河卒 [题目考点] 1. 坐标 ...

  7. 信息学奥赛一本通 1967:【14NOIP普及组】螺旋矩阵 | 洛谷 P2239 [NOIP2014 普及组] 螺旋矩阵

    [题目链接] ybt 1967:[14NOIP普及组]螺旋矩阵 洛谷 P2239 [NOIP2014 普及组] 螺旋矩阵 类似考题: 洛谷 P1014 [NOIP1999 普及组] Cantor 表 ...

  8. 信息学奥赛一本通 1919:【02NOIP普及组】选数 | 洛谷 P1036 [NOIP2002 普及组] 选数

    [题目链接] ybt 1919:[02NOIP普及组]选数 洛谷 P1036 [NOIP2002 普及组] 选数 [题目考点] 1.排列组合 2.深搜(子集树) 3.质数 [解题思路] 深搜(子集树) ...

  9. 信息学奥赛一本通 1118:铺地毯 | 1863:【11NOIP提高组】铺地毯 | OpenJudge NOI 1.9 14 | 洛谷 P1003 [NOIP2011 提高组] 铺地毯

    [题目链接] ybt 1118:铺地毯 ybt 1863:[11NOIP提高组]铺地毯 OpenJudge NOI 1.9 14:铺地毯 洛谷 P1003 [NOIP2011 提高组] 铺地毯 [题目 ...

最新文章

  1. Golang 微框架 Gin 简介
  2. 游戏开发基础:A*算法(转)
  3. mysqldump备份数据库
  4. MUI多端发布开发指南(终于把MUI的使用场景说清楚了)
  5. Fast R-CNN: 我变快了,也变强了!
  6. reuse project css less path folder hierarchy issue
  7. svg配合css3动画_带有Adobe Illustrator,HTML和CSS的任何网站的SVG动画
  8. php date当天,php5中date()获得的时间不是当前时间的解决方法
  9. php写linux应用程序,Linux应用程序使用写文件调试程序的方法
  10. shell获取命令返回结果前多少行(n行)
  11. Web应用程序框架 Apache Click
  12. excel导入mysql教程视频_excel数据导入SQL数据库的教程
  13. 超级实用网址合集(必须收藏一波)
  14. YOLOX-PAI: An Improved YOLOX, Stronger and Faster than YOLOv6
  15. 企业愿景不能是忽悠,而是发自内心的渴望
  16. U8接口开发-五大方式对比与剖析
  17. Spring 循环依赖(circular dependency)
  18. pair类型 C++
  19. JoyStick游戏杆编程实践
  20. Java复习笔记(8)——网络

热门文章

  1. IIS 中 另一个程序正在使用此文件,进程无法访问!
  2. SQLServer2005出了点怪事~(应该是编码问题~)
  3. 手绘导图版:深入解析机器学习在风控场景中的8大应用
  4. 人口简史:我们曾经差一点就彻底没了
  5. 4段简短代码教你用Python读写Excel
  6. 《2018区块链整体架构及应用》(PPT全文)
  7. RTX移植到STM32F103
  8. ​Redis的各种“坑”,你知道多少?
  9. 粉丝福利,送10个程序员专用机械键盘
  10. 原创|面试官:线上服务器CPU占用率高如何排查定位问题?