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. 遇到数字,直接放入答案序列

  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
}

2019.06.17课件:[洛谷P1310]表达式的值 题解的更多相关文章

  1. 洛谷P1310 表达式的值 题解 栈/后缀表达式的应用

    题目链接:https://www.luogu.org/problem/P1310 本题涉及算法:栈.前缀表达式转后缀表达式,动态规划思想. 这道题目我思考了好长时间,第一时间让我做的话我也做不出来. ...

  2. 洛谷P1310 表达式的值——题解

    题目传送 题的难点:1.有运算优先级,不好判断.2.有破坏整体和谐性的讨厌的括号.3.不知道哪里要填数.4.要求方案数很大,搜索不会做呐. 发现难点1和2都是中缀表达式的缺点.转成后缀表达式后难点1. ...

  3. 洛谷 P1310 表达式的值 解题报告

    P1310 表达式的值 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. "× "运算优先于"⊕"运算,即计算表 ...

  4. 洛谷P1310 表达式的值

    P1310 表达式的值 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算.例 ...

  5. 【洛谷P1310 表达式的值】

    题目链接 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算.例如:计算表达式 ...

  6. 洛谷P1981 表达式求值 题解 栈/中缀转后缀

    题目链接:https://www.luogu.org/problem/P1981 这道题目就是一道简化的中缀转后缀,因为这里比较简单,只有加号(+)和乘号(*),所以我们只需要开一个存放数值的栈就可以 ...

  7. 洛谷 P1981 表达式求值

    P1981 表达式求值 题目描述 给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值. 输入输出格式 输入格式: 输入文件为 expr.in. 输入仅有一行,为需要你计算的表达式,表达式中只 ...

  8. 题解-洛谷P1981 表达式求值(模拟+处理优先级的递归)

    https://www.luogu.org/problemnew/show/P1981 (原题链接) 显然乘法的优先级高与加法,因此碰到乘号就要优先把一连串与乘号相连的数算出,很容易想到递归.可用普通 ...

  9. 洛谷 P1981 表达式求值(模拟)

    嗯... 题目链接:https://www.luogu.org/problem/P1981 这道题其实是数组模拟栈.首先处理乘法:注意从后往前处理,处理后归零.然后把数都加起来即可. AC代码: #i ...

随机推荐

  1. Warning | 3719 | 'utf8' is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous.

    MySQL的“utf8”实际上不是真正的UTF-8.“utf8”只支持每个字符最多三个字节,而真正的UTF-8是每个字符最多四个字节. MySQL一直没有修复这个bug,他们在2010年发布了一个叫作 ...

  2. JDK环境变量配置linux

    安装前先查看是否安装过jdk如果安装过则 卸载 1. 确定JDK的版本: rpm -qa | grep jdk rpm -qa | grep gcj 可能的结果是: libgcj-4.1.2-42.e ...

  3. P1351 联合权值[鬼畜解法]

    题目描述 无向连通图 G 有 n 个点,n−1 条边.点从 1 到 n 依次编号,编号为 i 的点的权值为 Wi​,每条边的长度均为 1.图上两点 (u,v) 的距离定义为 u 点到 v 点的最短距离 ...

  4. 【Java】聚合和组合的区别

    组合:部分与整体是与生俱来的,部分的存在依赖于整体.比如人与人的某个器官,人一出生,器官就在,人死亡,器官也就没了意义. 聚合:你与你的电脑(或者其它物品),电脑是属于你的吧,但是你是一出生就拥有了电 ...

  5. P2P system: FastTrack and BitTorrent

    FastTrack FastTrack来源于Gnutella,是Gnutella 和 Napster的杂交体 有些node承担了更重要的责任,这些nodes称为supernodes,因为这些改进,它比 ...

  6. mysql对子查询的优化改写

    重庆SEO:<高性能mysql第三版>提到mysql会将in子查询改写成exists查询(书中基于的mysql版本是5.1.50和5.5) 但是在5.6之后,已经优化成使用半连接查询 SE ...

  7. POJ-2689-Prime Distance(素数区间筛法)

    链接: https://vjudge.net/problem/POJ-2689 题意: The branch of mathematics called number theory is about ...

  8. [ARIA] Accessible modal dialogs

    Learn how to create a modal dialog with accessible keyboard and screen reader mechanics using the na ...

  9. pyexcel_xlsx

    from pyexcel_xlsx import get_data,save_data excel_data = get_data('xxxx.xlsx文件存储位置') #得到的excel_data是 ...

  10. Poj 2114 Boatherds(点分治)

    Boatherds Time Limit: 2000MS Memory Limit: 65536K Description Boatherds Inc. is a sailing company op ...