zoukankan      html  css  js  c++  java
  • 2019.06.17课件:[洛谷P1310]表达式的值 题解

    P1310 表达式的值

    题目描述

    给你一个带括号的布尔表达式,其中+表示或操作|*表示与操作&,先算*再算+。但是待操作的数字(布尔值)不输入。

    求能使最终整个式子的值为0的方案数。

    题外话

    不久之前我在codewars上做过一道类似的题目。

    以及把它搬运到了洛谷上。

    布尔表达式计数问题

    考虑这样一个问题:

    有两个布尔变量(x)(y)

    我们知道使(x)等于1的方案有(x_1)种,等于0的方案有(x_0)种;使(y)等于1的方案有(y_1)种,等于0的方案有(y_0)种。

    那么:

    使(x&y)为1的方案数?为0的方案数?

    使(x|y)为1的方案数?为0的方案数?

    使(xoplus 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)

    使(xoplus 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+++。

    正确性?

    [egin{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
    }
    
  • 相关阅读:
    520了,用32做个简单的小程序
    年轻就该多尝试,教你20小时Get一项新技能
    自定义注解!绝对是程序员装逼的利器!!
    vs2015添加ActiveX Control Test Container工具(转载)
    编译MapWinGis
    C#遍历集合与移除元素的方法
    c#winform程序,修改MessageBox提示框中按钮的文本
    java程序员面试答题技巧
    什么是DOM
    uml类关系
  • 原文地址:https://www.cnblogs.com/water-lift/p/11038885.html
Copyright © 2011-2022 走看看