信息学奥赛一本通 1956:【11NOIP普及组】表达式的值 | 洛谷 P1310 [NOIP2011 普及组] 表达式的值
【題目鏈接】
ybt 1956:【11NOIP普及組】表達式的值
洛谷 P1310 [NOIP2011 普及組] 表達式的值
【題目考點】
由帶括號的中綴表達式構建表達式樹
【解題思路】
思路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 普及组] 表达式的值的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信息学奥赛一本通(1183:病人排队)
- 下一篇: 信息学奥赛一本通(1110:查找特定的值