2019.06.17课件:[洛谷P1310]表达式的值 题解

P1310 表达式的值

题目描述

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

  2. 遇到左括号,入栈

  3. 遇到右括号,把栈顶到上一个左括号的元素依次出栈并放入答案序列
  4. 遇到乘号,入栈
  5. 遇到加号,从栈顶开始弹出这段连续的乘号,并放入答案序列,最后加号入栈
  6. 最后把栈里剩下的元素依次放入答案序列

为何是正确的?

模拟\(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} \]

P1310题解

首先在输入的表达式的恰当位置插入未知变量,而后转为后缀表达式。固然也能够一边转,一边插入未知变量。

以后,咱们计算这个后缀表达式的值。不过维护的信息再也不是表达式的值,而是使表达式值为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
}
相关文章
相关标签/搜索