給你一個帶括號的布爾表達式,其中+
表示或操做|
,*
表示與操做&
,先算*
再算+
。可是待操做的數字(布爾值)不輸入。c++
求能使最終整個式子的值爲0的方案數。spa
不久以前我在codewars上作過一道相似的題目。c++11
以及把它搬運到了洛谷上。code
考慮這樣一個問題:blog
有兩個布爾變量\(x\)和\(y\)。get
咱們知道使\(x\)等於1的方案有\(x_1\)種,等於0的方案有\(x_0\)種;使\(y\)等於1的方案有\(y_1\)種,等於0的方案有\(y_0\)種。string
那麼:it
使\(x\&y\)爲1的方案數?爲0的方案數?table
使\(x|y\)爲1的方案數?爲0的方案數?class
使\(x\oplus y\)(一般咱們使用\(\oplus\)表示異或)爲1的方案數?爲0的方案數?
不難發現:
使\(x\&y\)爲1,那麼\(x\)和\(y\)都要爲1,因此方案數爲\(x_1*y_1\)。
使\(x\&y\)爲0,那麼\(x\)和\(y\)不能都爲1,因此方案數爲\(x_1*y_0+x_0*y_1+x_0*y_0\)。
使\(x|y\)爲1的方案數爲\(x_1*y_1+x_0*y_1+x_1*y_0\),爲0的方案數爲\(x_0*y_0\)。
使\(x\oplus y\)爲1的方案數爲\(x_0*y_1+x_1*y_0\),爲0的方案數爲\(x_0*y_0+x_1*y_1\)。
\((1+2)*4\)
如上圖,每一個葉節點是一個數字,其餘節點都是(雙目)運算符。
整棵樹表示一個表達式。每一個子樹表示一個子表達式。
計算這個表達式的方式以下圖。
因此值爲12。
中序遍歷這個表達式樹,咱們發現獲得的結果幾乎和原來的表達式同樣。
只是須要加一些括號罷了。
處理方法:咱們能夠給每一個子樹先後都加一對括號。
稱前序遍歷獲得的式子爲前綴表達式,或者波蘭表達式。稱後序遍歷獲得的式子爲後綴表達式,或者逆波蘭表達式。
前綴表達式和後綴表達式都擁有一個優秀的性質:不須要括號。
(下面僅之後綴表達式爲例)
好比上文的\((1+2)*4\),改成後綴表達式就是:\(1\ 2\ +\ 4\ *\)。
咱們能夠用棧來處理:
遇到數字,入棧;遇到符號,從棧裏取出兩個數字,按照這個符號運算,而後把結果入棧。最後棧裏剩下的就是結果。
\(1\ 2\ +\ 4\ *\)的計算過程以下:
1入棧 | 1 | |
2入棧 | 1 | 2 |
1 2出棧,相加得3,3入棧 | 3 | |
4入棧 | 3 | 4 |
3 4出棧,相乘得12,12入棧 | 12 |
因此答案是12。
你能夠直接建樹,跑後序遍歷。
可是這樣又很差寫,又慢。
咱們考慮用棧維護。
遍歷中綴表達式:
遇到數字,直接放入答案序列
遇到左括號,入棧
最後把棧裏剩下的元素依次放入答案序列
模擬\(1+1*2*(1+2)+3*2*(1*5)+1\)
說明 | 棧 | 答案序列 |
---|---|---|
1放入答案序列 | 1 | |
+入棧 | + | 1 |
1放入答案序列 | + | 11 |
*入棧 | +* | 11 |
2放入答案序列 | +* | 112 |
*入棧 | +** | 112 |
(入棧 | +**( | 112 |
1放入答案序列 | +**( | 1121 |
+入棧 | +**(+ | 1121 |
2放入答案序列 | +**(+ | 11212 |
出現),+出棧並放入答案序列,(出棧 | +** | 11212+ |
出現+,彈出棧頂的*並放入答案序列,而後+入棧 | ++ | 11212+** |
3放入答案序列 | ++ | 11212+**3 |
*入棧 | ++* | 11212+**3 |
2放入答案序列 | ++* | 11212+**32 |
*入棧 | ++** | 11212+**32 |
(入棧 | ++**( | 11212+**32 |
1放入答案序列 | ++**( | 11212+**321 |
*入棧 | ++**(* | 11212+**321 |
5放入答案序列 | ++**(* | 11212+**3215 |
出現),*出棧並放入答案序列,(出棧 | ++** | 11212+**3215* |
出現+,彈出棧頂的*並放入答案序列,而後+入棧 | +++ | 11212+**3215*** |
1放入答案序列 | +++ | 11212+**3215***1 |
剩餘棧中元素放入答案序列 | 11212+**3215***1+++ |
因此答案是11212+**3215***1+++。
正確性?
\[ \begin{aligned} 11212+**3215***1+++&=112(12+)**32(15*)**1+++\\ &=1123**325**1+++\\ &=11(23*)*3(25*)*1+++\\ &=116*3(10)*1+++\\ &=1(16*)(3(10)*)1+++\\ &=16(30)1+++\\ &=16(31)++\\ &=1(37)+\\ &=38\\ \\ 1+1*2*(1+2)+3*2*(1*5)+1&=1+2*3+6*5+1\\ &=1+6+30+1\\ &=38\\ \end{aligned} \]
首先在輸入的表達式的恰當位置插入未知變量,而後轉爲後綴表達式。固然也能夠一邊轉,一邊插入未知變量。
以後,咱們計算這個後綴表達式的值。不過維護的信息再也不是表達式的值,而是使表達式值爲0或1的方案數。
注意到單個變量爲0或1的方案數爲1.
#include <bits/stdc++.h> using namespace std; inline void read(int &num) { bool flag = 0; num = 0; char c = getchar(); while ((c < '0' || c > '9') && c != '-') c = getchar(); if (c == '-') { flag = 1; c = getchar(); } num = c - '0'; c = getchar(); while (c >= '0' && c <= '9') num = (num << 3) + (num << 1) + c - '0', c = getchar(); if (flag) num *= -1; } inline void output(int num) { if (num < 0) { putchar('-'); num = -num; } if (num >= 10) output(num / 10); putchar(num % 10 + '0'); } inline void outln(int num) { output(num); puts(""); } inline void outln(string str) { puts(str.c_str()); } //以上爲頭文件和快讀 const int mod = 10007; const int N = 100001; int n; char str[N]; //輸入的中綴表達式 stack<char> sta; //轉後綴表達式時使用的棧 string final; //後綴表達式(答案序列) stack<int> zero, one; //zero維護使表達式值爲0的方案個數,one維護使表達式值爲1的方案個數 int main() { read(n); scanf("%s", str + 1); final.push_back('n'); //後綴表達式最開始應該有一個未知變量 for (int i = 1; i <= n; i++) { if (str[i] == '(' || str[i] == '*') //遇到左括號或乘號,入棧 sta.push(str[i]); if (str[i] == '+') //遇到加號,彈出棧頂的乘號,而後加號入棧 { while (!sta.empty() && sta.top() == '*') { final.push_back(sta.top()); sta.pop(); } sta.push(str[i]); } if (str[i] == ')') //右括號,把到上一個左括號的元素出棧放入答案序列 { while (sta.top() != '(') { final.push_back(sta.top()); sta.pop(); } sta.pop(); } if (str[i] != '(' && str[i] != ')') //當不是左括號或者右括號時,應該插入一個未知變量 { final.push_back('n'); } } while (!sta.empty()) //剩下的元素放入答案序列 { final.push_back(sta.top()); sta.pop(); } for (char c : final) //遍歷後綴表達式,這裏使用了c++11的寫法,至關於 // for (int i = 0; i < final.size(); i++) // { char c = final[i]; { if (c == 'n') //單個變量,方案數爲1 { one.push(1); zero.push(1); } else { //rone表示右操做數(即上文中的y)爲1的方案數(即上文中的y1),rzero同理 int rone = one.top(), rzero = zero.top(); one.pop(); zero.pop(); //同理 int lone = one.top(), lzero = zero.top(); one.pop(); zero.pop(); if (c == '*') //與操做,爲1須要都爲1,爲0須要不都爲1 { one.push(lone * rone % mod); zero.push((lone * rzero % mod + lzero * rone % mod + lzero * rzero % mod) % mod); } else //或操做,爲0須要都爲0,爲1須要不都爲0 { zero.push(lzero * rzero % mod); one.push((lone * rzero % mod + lzero * rone % mod + lone * rone % mod) % mod); } } } outln(zero.top());//須要整個表達式的值爲0 }