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的方案数?
使\(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+++。
正确性?
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
}
												
											2019.06.17课件:[洛谷P1310]表达式的值 题解的更多相关文章
- 洛谷P1310 表达式的值 题解 栈/后缀表达式的应用
		
题目链接:https://www.luogu.org/problem/P1310 本题涉及算法:栈.前缀表达式转后缀表达式,动态规划思想. 这道题目我思考了好长时间,第一时间让我做的话我也做不出来. ...
 - 洛谷P1310 表达式的值——题解
		
题目传送 题的难点:1.有运算优先级,不好判断.2.有破坏整体和谐性的讨厌的括号.3.不知道哪里要填数.4.要求方案数很大,搜索不会做呐. 发现难点1和2都是中缀表达式的缺点.转成后缀表达式后难点1. ...
 - 洛谷 P1310 表达式的值 解题报告
		
P1310 表达式的值 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. "× "运算优先于"⊕"运算,即计算表 ...
 - 洛谷P1310 表达式的值
		
P1310 表达式的值 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算.例 ...
 - 【洛谷P1310 表达式的值】
		
题目链接 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算.例如:计算表达式 ...
 - 洛谷P1981 表达式求值 题解 栈/中缀转后缀
		
题目链接:https://www.luogu.org/problem/P1981 这道题目就是一道简化的中缀转后缀,因为这里比较简单,只有加号(+)和乘号(*),所以我们只需要开一个存放数值的栈就可以 ...
 - 洛谷 P1981 表达式求值
		
P1981 表达式求值 题目描述 给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值. 输入输出格式 输入格式: 输入文件为 expr.in. 输入仅有一行,为需要你计算的表达式,表达式中只 ...
 - 题解-洛谷P1981 表达式求值(模拟+处理优先级的递归)
		
https://www.luogu.org/problemnew/show/P1981 (原题链接) 显然乘法的优先级高与加法,因此碰到乘号就要优先把一连串与乘号相连的数算出,很容易想到递归.可用普通 ...
 - 洛谷 P1981 表达式求值(模拟)
		
嗯... 题目链接:https://www.luogu.org/problem/P1981 这道题其实是数组模拟栈.首先处理乘法:注意从后往前处理,处理后归零.然后把数都加起来即可. AC代码: #i ...
 
随机推荐
- 一个让Java事半功倍的反射库
			
在Java和Android中,我们常常会使用反射来达到一些兼容的目的.Java原生提供的反射很是麻烦,使用起来很是不方便.比如我们想要调UserManager的静态方法get,使用原生的实现如下 tr ...
 - Python memory error的问题
			
前段时间在读取一个文件的过程中,竟然出现了Memory Error!简直让我怀疑自己买了个假电脑,毕竟是8G内存i7处理器,一度怀疑自己装了假的内存条....下面说一下几个解题步骤....一般就是用下 ...
 - 定制ubuntu镜像
			
使用ubuntu server 18.04 lts版镜像 作为源镜像定制目标镜像, 工具cubic, 定制目标镜像, 使得一些服务装机后即可使用,例如redis, mysql, monggodb ,s ...
 - win10家庭版本不能连接远程桌面
			
出现身份验证错误 要求的函数不受支持,CredSSP 加密 Oracle 修正 然后网上大多数教程是叫你修改组策略,然后试过坑的我发现自己的家庭版没有组策略,天真的我在网上找寻了装组策略的代码,一顿捣 ...
 - Makefile中 的 phony target,empty target
			
phony target Makefile的语法很简单 <target> : <prerequisites> [tab] <commands> 目标.先决条件.命令 ...
 - yum nginx最新版安装
			
去官方站点获取yum仓库 [root@web01 ~]# cat /etc/yum.repos.d/nginx.repo [nginx] name=nginx repo baseurl=http:// ...
 - 某个Spring Cloud分布式架构
 - MyBatis_[tp_50]_动态sql_bind绑定 与原生sql对比
			
笔记要点出错分析与总结 更推荐,原生的sql写法,bind方法不灵活! Test中: e.setLastName("%e%"); 直接在这里写上模糊查询的语句,更加省时 配置中: ...
 - hihocoder 1931 最短管道距离
			
描述 在一张2D地图上有N座城市,坐标依次是(X1, Y1), (X2, Y2), ... (XN, YN). 现在H国要修建一条平行于X轴的天然气主管道.这条管道非常长,可以认为是一条平行于X轴的直 ...
 - python打造批量关键词排名查询工具
			
自己做站点的时候,都看看收录和关键词排名什么的,所以打造的这个批量关键词查询工具. #encoding:utf-8 import urllib,re,random,time,sys,StringIO, ...