给你一个带括号的布尔表达式,其中+
表示或操做|
,*
表示与操做&
,先算*
再算+
。可是待操做的数字(布尔值)不输入。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 }