This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010, M = N << 1;
int n; // 变量个数
int w[N]; // 变量参数的值
char c[N]; // 操作符栈
int stk[N], tt; // 数字栈
bool st[N]; // 是不是对
// 邻接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 第一次dfs求原后缀表达式,记录每个节点的计算值
int dfs1(int u) {
if (u <= n) return w[u]; // 叶子节点,返回参数值
if (c[u] == '!') // 如果当前节点是运算符!的话,那么,它一定只有一个子节点,节点号:h[u],值:e[h[u]],取反返回即可
w[u] = !dfs1(e[h[u]]);
else {
// 与和或
int a = e[h[u]], b = e[ne[h[u]]]; // 取当前节点左儿子和右儿子
if (c[u] == '&')
w[u] = dfs1(a) & dfs1(b); // 左儿子与右儿子的与运算结果 
else
w[u] = dfs1(a) | dfs1(b); // 左儿子与右儿子的或运算结果 
}
return w[u]; // 返回计算结果值
}
// 从根开始,标记哪些节点影响表达式的值
void dfs2(int u) {
st[u] = true; // 因为
if (u <= n) return; // 递归到叶子返回
if (c[u] == '!') { // 取反操作符
dfs2(e[h[u]]); // 前进,继续标记节点
return;
}
int a = e[h[u]], b = e[ne[h[u]]]; // 左右儿子节点号
if (c[u] == '&') { // &运算
if (w[a]) dfs2(b); // 左儿子=1,对右子树递归标记
if (w[b]) dfs2(a); // 右儿子=1,对左子树递归标记
} else { // |运算
if (!w[a]) dfs2(b); // 左儿子=0,递归右子树,右子树中的某些节点变化会对根造成影响
if (!w[b]) dfs2(a); // 右儿子=0,递归左儿子,左子树中的某些节点变化会对根造成影响
}
}
int main() {
string s;
getline(cin, s);
cin >> n; // 参数个数
for (int i = 1; i <= n; i++) scanf("%d", &w[i]); // 每个参数对应的数值
memset(h, -1, sizeof h); // 邻接表初始化
// 为了创建一个表达式树,就需要给每个节点创建一个编号。现在已知数字节点,也就是叶子节点数量是n
// 所以,运算符的编号就是从++m开始的。
int m = n;
// 后缀表达式->栈->建图
// 利用栈进行辅助建图,图才能进行dfs计算
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') continue;
if (s[i] == 'x') {
int k = 0;
i++; // 跳过x
while (i < s.size() && isdigit(s[i])) k = k * 10 + s[i++] - '0';
stk[++tt] = k;
} else if (s[i] == '!') {
c[++m] = s[i]; //++m这个节点,表达式树中是s[i]这个操作符,
/* 表达式树:
(1)每个叶子节点的数值
(2)非叶子节点需要记录是什么操作符
记录办法
(1)以树中的节点编号为索引,[1~n]为叶子,[n+1~]为操作符
(2)再开一个数组char c[],记录操作符节点是哪个操作符
*/
add(m, stk[tt--]); // 从栈中弹出一个数字,因为是!嘛,树是由上到下的连单向边
stk[++tt] = m; // m节点入栈,方便后续构建
} else {
c[++m] = s[i];
add(m, stk[tt--]); // 与!不同,需要由m引向两个节点各一条边
add(m, stk[tt--]);
stk[++tt] = m;
}
}
// 计算原式结果
int res = dfs1(m); // 这个m才是根,因为是后缀表达式
// 标记哪些节点影响最终结果
dfs2(m);
for (int i = 1; i <= m; i++) {
cout << "i=" << i << ",st[" << i << "]=" << st[i] << ",c[" << i << "]=" << c[i] << endl;
}
int Q;
cin >> Q; // 询问个数
while (Q--) {
int x;
cin >> x; // 修改哪个变量
if (st[x]) // 如果x被打过标记,那么它的变化将会影响根节点的值,对根节点取反即可
printf("%d\n", !res);
else // 不会影响根节点的值
printf("%d\n", res);
}
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1,231 @@
## [$AcWing$ $2769$. 表达式](https://www.acwing.com/problem/content/description/2771/)
### 一、题目描述
小 $C$ 热衷于学习数理逻辑。
有一天,他发现了一种特别的逻辑表达式。
在这种逻辑表达式中,所有操作数都是变量,且它们的取值只能为 $0$ 或 $1$,运算从左往右进行。
如果表达式中有括号,则先计算括号内的子表达式的值。
特别的,这种表达式有且仅有以下几种运算:
与运算:$a$ & $b$。当且仅当 $a$ 和 $b$ 的值都为 $1$ 时,该表达式的值为 $1$。其余情况该表达式的值为 $0$。
或运算:$a$ | $b$。当且仅当 $a$ 和 $b$ 的值都为 $0$
时,该表达式的值为 $0$。其余情况该表达式的值为 $1$。
取反运算:$!a$。当且仅当 $a$ 的值为 $0$ 时,该表达式的值为 $1$。其余情况该表达式的值为 $0$。
小 $C$ 想知道,给定一个逻辑表达式和其中每一个操作数的初始取值后,再取反某一个操作数的值时,原表达式的值为多少。
为了化简对表达式的处理,我们有如下约定:表达式将采用后缀表达式的方式输入。
后缀表达式的定义如下:
如果 $E$ 是一个操作数,则 $E$ 的后缀表达式是它本身。
如果 $E$ 是 $E1$ $op$ $E2$ 形式的表达式,其中 $op$ 是任何二元操作符,且优先级不高于 $E1$、$E2$ 中括号外的操作符,则 $E$ 的后缀式为 $E$′$1$ $E$′$2$ $op$
,其中 $E$′$1$、$E$′$2$ 分别为 $E1$、$E2$ 的后缀式。
如果 $E$ 是 ($E1$) 形式的表达式,则 $E1$ 的后缀式就是 $E$ 的后缀式。
同时为了方便,输入中:
$a$) 与运算符(&)、或运算符(|)、取反运算符(!)的左右均有一个空格,但表达式末尾没有空格。
$b$) 操作数由小写字母 $x$ 与一个正整数拼接而成,正整数表示这个变量的下标。例如:$x10$,表示下标为 $10$ 的变量 $x10$。
数据保证每个变量在表达式中出现恰好一次。
**输入格式**
第一行包含一个字符串 $s$,表示上文描述的表达式。
第二行包含一个正整数 $n$,表示表达式中变量的数量。表达式中变量的下标为 $1$,$2$,…,$n$。
第三行包含 $n$ 个整数,第 $i$ 个整数表示变量 $xi$ 的初值。
第四行包含一个正整数 $q$,表示询问的个数。
接下来 $q$ 行,每行一个正整数,表示需要取反的变量的下标。
注意,每一个询问的修改都是临时的,即之前询问中的修改不会对后续的询问造成影响。
数据保证输入的表达式合法。
变量的初值为 $0$ 或 $1$。
**输出格式**
输出一共有 $q$ 行,每行一个 $0$ 或 $1$,表示该询问下表达式的值。
**数据范围**
对于 $20$% 的数据,表达式中有且仅有与运算(&)或者或运算(|)。
对于另外 $30$% 的数据,|$s$|≤$1000$,$q$≤$1000$,$n$≤$1000$。
对于另外 $20$% 的数据,变量的初值全为 $0$ 或全为 $1$。
对于 $100$% 的数据,$1$≤|$s$|≤$1$×$106$,$1$≤$q$≤$1$×$105$,$2$≤$n$≤$1$×$10^5$。
其中,|$s$| 表示字符串 $s$ 的长度。
**输入样例1**:
```cpp {.line-numbers}
x1 x2 & x3 |
3
1 0 1
3
1
2
3
```
**输出样例1**:
```cpp {.line-numbers}
1
1
0
```
### 二、解题思路
首先发现临时修改,自然地想到了先处理整个表达式的值,再考虑临时修改是否会有影响。
先建一颗表达式树。
我们发现:
对于一个有影响的子树
**运算符号$\&$**
- 如果左右子树都是$1$,两个子树有一个值变更,都会影响根的值,打$tag$
- 如果左右子树其中一个是$0$,$0$子树更改会影响根的值,打$tag$
**运算符号$|$**
- 如果两个都是$0$,两个子树有一个值变更,都会根的值,打$tag$
- 如果其中一个是$1$,$1$的子树变更会对根造成影响,打$tag$
**运算符号取反$!$**
直接取反即可
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202310131650716.png)
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010, M = N << 1;
int n; // 变量个数
int w[N]; // 变量参数的值
char c[N]; // 操作符栈
int stk[N], tt; // 数字栈
bool st[N]; // 是不是对
// 邻接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 第一次dfs求原后缀表达式,记录每个节点的计算值
int dfs1(int u) {
if (u <= n) return w[u]; // 叶子节点,返回参数值
if (c[u] == '!') // 如果当前节点是运算符!的话,那么,它一定只有一个子节点,节点号:h[u],值:e[h[u]],取反返回即可
w[u] = !dfs1(e[h[u]]);
else {
// 与和或
int a = e[h[u]], b = e[ne[h[u]]]; // 取当前节点左儿子和右儿子
if (c[u] == '&')
w[u] = dfs1(a) & dfs1(b); // 左儿子与右儿子的与运算结果 
else
w[u] = dfs1(a) | dfs1(b); // 左儿子与右儿子的或运算结果 
}
return w[u]; // 返回计算结果值
}
// 从根开始,标记哪些节点影响表达式的值
void dfs2(int u) {
st[u] = true; // 因为
if (u <= n) return; // 递归到叶子返回
if (c[u] == '!') { // 取反操作符
dfs2(e[h[u]]); // 前进,继续标记节点
return;
}
int a = e[h[u]], b = e[ne[h[u]]]; // 左右儿子节点号
if (c[u] == '&') { // &运算
if (w[a]) dfs2(b); // 左儿子=1,对右子树递归标记
if (w[b]) dfs2(a); // 右儿子=1,对左子树递归标记
} else { // |运算
if (!w[a]) dfs2(b); // 左儿子=0,递归右子树,右子树中的某些节点变化会对根造成影响
if (!w[b]) dfs2(a); // 右儿子=0,递归左儿子,左子树中的某些节点变化会对根造成影响
}
}
int main() {
string s;
getline(cin, s);
cin >> n; // 参数个数
for (int i = 1; i <= n; i++) scanf("%d", &w[i]); // 每个参数对应的数值
memset(h, -1, sizeof h); // 邻接表初始化
// 为了创建一个表达式树,就需要给每个节点创建一个编号。现在已知数字节点,也就是叶子节点数量是n
// 所以,运算符的编号就是从++m开始的。
int m = n;
// 后缀表达式->栈->建图
// 利用栈进行辅助建图,图才能进行dfs计算
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') continue;
if (s[i] == 'x') {
int k = 0;
i++; // 跳过x
while (i < s.size() && isdigit(s[i])) k = k * 10 + s[i++] - '0';
stk[++tt] = k;
} else if (s[i] == '!') {
c[++m] = s[i]; //++m这个节点,表达式树中是s[i]这个操作符,
/* 表达式树:
(1)每个叶子节点的数值
(2)非叶子节点需要记录是什么操作符
记录办法
(1)以树中的节点编号为索引,[1~n]为叶子,[n+1~]为操作符
(2)再开一个数组char c[],记录操作符节点是哪个操作符
*/
add(m, stk[tt--]); // 从栈中弹出一个数字,因为是!嘛,树是由上到下的连单向边
stk[++tt] = m; // m节点入栈,方便后续构建
} else {
c[++m] = s[i];
add(m, stk[tt--]); // 与!不同,需要由m引向两个节点各一条边
add(m, stk[tt--]);
stk[++tt] = m;
}
}
// 计算原式结果
int res = dfs1(m); // 这个m才是根,因为是后缀表达式
// 标记哪些节点影响最终结果
dfs2(m);
for (int i = 1; i <= m; i++) {
cout << "i=" << i << ",st[" << i << "]=" << st[i] << ",c[" << i << "]=" << c[i] << endl;
}
int Q;
cin >> Q; // 询问个数
while (Q--) {
int x;
cin >> x; // 修改哪个变量
if (st[x]) // 如果x被打过标记,那么它的变化将会影响根节点的值,对根节点取反即可
printf("%d\n", !res);
else // 不会影响根节点的值
printf("%d\n", res);
}
return 0;
}
```

View File

@@ -0,0 +1,84 @@
#include <bits/stdc++.h>
using namespace std;
// 因为要对10000取模,注意在表达式求值模板的基础上,不断取模,要考虑乘法,加法导致的int溢出问题,开long long解决。
#define int long long
const int MOD = 10000;
/*
测试用例I:
(2+2)*(1+1)
答案:8
测试用例II:
2+(3*4)-((5*9-5)/8-4)
答案:13
*/
stack<int> stk; // 数字栈
stack<char> op; // 操作符栈
// 优先级表
unordered_map<char, int> g{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
/**
* 功能:计算两个数的和差积商
*/
void eval() {
int a = stk.top(); // 第二个操作数
stk.pop();
int b = stk.top(); // 第一个操作数
stk.pop();
char p = op.top(); // 运算符
op.pop();
int r; // 结果
// 计算结果
if (p == '+') r = (b + a) % MOD;
if (p == '-') r = (b - a) % MOD;
if (p == '*') r = (b * a) % MOD;
if (p == '/') r = (b / a) % MOD;
// 结果入栈
stk.push(r);
}
signed main() {
// 读入表达式
string s;
cin >> s;
// 遍历字符串的每一位
for (int i = 0; i < s.size(); i++) {
// ① 如果是数字,则入栈
if (isdigit(s[i])) {
// 读出完整的数字
int x = 0;
while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
x = x * 10 + s[i] - '0';
i++;
}
i--; // 加多了一位,需要减去
stk.push(x % MOD); // 数字入栈
}
// ② 左括号无优先级,入栈
else if (s[i] == '(')
op.push(s[i]);
// ③ 右括号时,需计算最近一对括号里面的值
else if (s[i] == ')') {
// 从栈中向前找,一直找到左括号
while (op.top() != '(') eval(); // 将左右括号之间的计算完,维护回栈里
// 左括号出栈
op.pop();
} else { // ④ 运算符
// 如果待入栈运算符优先级低,则先计算
while (op.size() && g[op.top()] >= g[s[i]]) eval();
op.push(s[i]); // 操作符入栈
}
}
while (op.size()) eval(); // ⑤ 剩余的进行计算
printf("%d\n", stk.top() % MOD); // 输出结果
}

View File

@@ -0,0 +1,80 @@
/*
测试用例1:
0&(1|0)|(1|1|1&0)
答案:
1
1 2
测试用例2:
(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0
答案:
0
2 3
*/
#include <bits/stdc++.h>
using namespace std;
struct Node {
int v, a, b; // v:代表当前的结果值,a: &短路的次数 b:|短路的次数
};
stack<Node> num;
stack<char> stk;
unordered_map<char, int> h{{'|', 1}, {'&', 2}};
void eval() {
// 这里要注意从栈中弹出元素的顺序,先出来的是y右子树,后出来的是x左子树
Node y = num.top();
num.pop();
Node x = num.top();
num.pop();
char p = stk.top();
stk.pop();
Node r;
if (p == '|') {
if (x.v == 1)
r = {x.v, x.a, x.b + 1};
else
r = {y.v, x.a + y.a, x.b + y.b};
} else {
if (x.v == 1)
r = {y.v, x.a + y.a, x.b + y.b};
else
r = {x.v, x.a + 1, x.b};
}
num.push(r);
}
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i])) {
int x = 0;
while (i < s.size() && isdigit(s[i])) {
x = x * 10 + s[i] - '0';
i++;
}
i--;
num.push({x, 0, 0});
} else if (s[i] == '(')
stk.push(s[i]);
else if (s[i] == ')') {
while (stk.top() != '(') eval();
stk.pop();
} else {
while (stk.size() && h[stk.top()] >= h[s[i]]) eval();
stk.push(s[i]);
}
}
while (stk.size()) eval();
printf("%d\n", num.top().v);
printf("%d %d\n", num.top().a, num.top().b);
return 0;
}

View File

@@ -0,0 +1,620 @@
### [$AcWing$ $4730$. 逻辑表达式](https://www.acwing.com/problem/content/description/4733/)
### 一、题目描述
逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。
在一个逻辑表达式中,元素的值只有两种可能:$0$(表示假)和 $1$(表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为 `&`)和“或”(符号为 `|`)。其运算规则如下:
$0 \mathbin{\&} 0 = 0 \mathbin{\&} 1 = 1 \mathbin{\&} 0 = 0$,$1 \mathbin{\&} 1 = 1$;
$0 \mathbin{|} 0 = 0$,$0 \mathbin{|} 1 = 1 \mathbin{|} 0 = 1 \mathbin{|} 1 = 1$。
在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,`&` 运算优先于 `|` 运算;同种运算并列时,从左向右运算。
比如,表达式 `0|1&0` 的运算顺序等同于 `0|(1&0)`;表达式 `0&1&0|1` 的运算顺序等同于 `((0&1)&0)|1`。
此外,在 $C++$ 等语言的有些编译器中,对逻辑表达式的计算会采用一种 **短路** 的策略:在形如 `a&b` 的逻辑表达式中,会先计算 `a` 部分的值,如果 $a = 0$,那么整个逻辑表达式的值就一定为 $0$,故无需再计算 `b` 部分的值;同理,在形如 `a|b` 的逻辑表达式中,会先计算 `a` 部分的值,如果 $a = 1$,那么整个逻辑表达式的值就一定为 $1$,无需再计算 `b` 部分的值。
现在给你一个逻辑表达式,你需要计算出它的值,并且统计出在计算过程中,两种类型的 **短路** 各出现了多少次。需要注意的是,如果某处 **短路** 包含在更外层被 **短路** 的部分内则不被统计,如表达式 `1|(0&1)` 中,尽管 `0&1` 是一处 **短路**,但由于外层的 `1|(0&1)` 本身就是一处 **短路**,无需再计算 `0&1` 部分的值,因此不应当把这里的 `0&1` 计入一处 **短路**。
#### 输入格式
输入共一行,一个非空字符串 $s$ 表示待计算的逻辑表达式。
#### 输出格式
输出共两行,第一行输出一个字符 `0` 或 `1`,表示这个逻辑表达式的值;第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 `a&b` 和 `a|b` 的“短路”各出现了多少次。
#### 样例输入 #1
```
0&(1|0)|(1|1|1&0)
```
#### 样例输出 #1
```
1
1 2
```
#### 样例输入 #2
```
(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0
```
#### 样例输出 #2
```
0
2 3
```
**【样例解释 \#1】**
该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:
```plain
0&(1|0)|(1|1|1&0)
=(0&(1|0))|((1|1)|(1&0)) //用括号标明计算顺序
=0|((1|1)|(1&0)) //先计算最左侧的 &,是一次形如 a&b 的“短路”
=0|(1|(1&0)) //再计算中间的 |,是一次形如 a|b 的“短路”
=0|1 //再计算中间的 |,是一次形如 a|b 的“短路”
=1
```
**【样例 \#3】**
见附件中的 `expr/expr3.in` 与 `expr/expr3.ans`。
**【样例 \#4】**
见附件中的 `expr/expr4.in` 与 `expr/expr4.ans`。
**【数据范围】**
设 $\lvert s \rvert$ 为字符串 $s$ 的长度。
对于所有数据,$1 \le \lvert s \rvert \le {10}^6$。保证 $s$ 中仅含有字符 `0`、`1`、`&`、`|`、`(`、`)` 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 $s$
中没有重复的括号嵌套(即没有形如 `((a))` 形式的子串,其中 `a` 是符合规范的逻辑表
达式)。
| 测试点编号 | $\lvert s \rvert \le$ | 特殊条件 |
| :----------: | :-------------------: | :------: |
| $1 \sim 2$ | $3$ | 无 |
| $3 \sim 4$ | $5$ | 无 |
| $5$ | $2000$ | 1 |
| $6$ | $2000$ | 2 |
| $7$ | $2000$ | 3 |
| $8 \sim 10$ | $2000$ | 无 |
| $11 \sim 12$ | ${10}^6$ | 1 |
| $13 \sim 14$ | ${10}^6$ | 2 |
| $15 \sim 17$ | ${10}^6$ | 3 |
| $18 \sim 20$ | ${10}^6$ | 无 |
其中:
特殊性质 1 为:保证 $s$ 中没有字符 `&`。
特殊性质 2 为:保证 $s$ 中没有字符 `|`。
特殊性质 3 为:保证 $s$ 中没有字符 `(` 和 `)`。
**【提示】**
以下给出一个“符合规范的逻辑表达式”的形式化定义:
- 字符串 `0` 和 `1` 是符合规范的;
- 如果字符串 `s` 是符合规范的,且 `s` 不是形如 `(t)` 的字符串(其中 `t` 是符合规范的),那么字符串 `(s)` 也是符合规范的;
- 如果字符串 `a` 和 `b` 均是符合规范的,那么字符串 `a&b`、`a|b` 均是符合规范的;
- 所有符合规范的逻辑表达式均可由以上方法生成。
### 二、题目解析
本题与 $NOIP2013$普及组复赛第二题《**[表达式求值](https://www.acwing.com/problem/content/456/)**》是亲属关系,
#### 关键词
中缀表达式转后缀表达式,后缀表达式求值
#### 前置试题
[$AcWing$ $3302$ 表达式求值](https://www.acwing.com/problem/content/3305/)
[【$2013$ $NOIP$普及组】表达式求值](http://ybt.ssoier.cn:8088/problem_show.php?pid=1962)
#### 1、中缀表达式转后缀表达式(四则运算+以空格隔开)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
// 中缀表达式转后缀表达式
/*
测试用例1:
a+b*c+(d*e+f)*g
答案:
abc*+de*f+g*+
测试用例2:
(6+3*(7-4))-8/2
答案:
6 3 7 4 - * + 8 2 / -
测试用例3:
(24*(9+6/38-5)+4)
答案:
24 9 6 38 / + 5 - * 4 +
*/
unordered_map<char, int> h{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; // 运算符级别表
string s; // 源串
string t; // 目标串
stack<char> stk; // 利用一个栈,完成中缀表达式转后缀表达式
int main() {
cin >> s;
for (int i = 0; i < s.size(); i++) {
// ①数字
if (isdigit(s[i])) {
int x = 0;
while (i < s.size() && isdigit(s[i])) {
x = x * 10 + s[i] - '0';
i++;
}
i--; // 走多了才能停止掉上面的while,需要回退一下
t.append(to_string(x));
} else if (isalpha(s[i])) // ②字符,比如a,b,c
t.push_back(s[i]);
else if (s[i] == '(') // ③左括号
stk.push(s[i]); // 左括号入栈
else if (s[i] == ')') { // ④右括号
while (stk.top() != '(') { // 让栈中元素(也就是+-*/和左括号)一直出栈,直到匹配的左括号出栈
t.push_back(stk.top());
stk.pop();
}
stk.pop(); // 左括号也需要出栈
} else {
// ⑤操作符 +-*/
while (stk.size() && h[s[i]] <= h[stk.top()]) { // 哪个操作符优先级高就先输出谁
t.push_back(stk.top());
stk.pop();
}
stk.push(s[i]); // 将自己入栈
}
}
// 当栈不为空时,全部输出
while (stk.size()) {
t.push_back(stk.top());
stk.pop();
}
// printf("%s", t.c_str());
cout << t << endl;
return 0;
}
```
#### 2、中缀表达式转后缀表达式(逻辑运算符+拷贝四则版本)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
/*
中缀的逻辑表达式 转 后缀的逻辑表达式
测试用例:
0&(0|1|0)
答案:
001|0|&
*/
unordered_map<char, int> h{{'|', 1}, {'&', 2}};
string s;
string t;
stack<char> stk;
int main() {
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i])) {
int x = 0;
while (i < s.size() && isdigit(s[i])) {
x = x * 10 + s[i] - '0';
i++;
}
i--;
t.append(to_string(x));
} else if (isalpha(s[i]))
t.push_back(s[i]);
else if (s[i] == '(')
stk.push(s[i]);
else if (s[i] == ')') {
while (stk.top() != '(') {
t.push_back(stk.top());
stk.pop();
}
stk.pop();
} else {
while (stk.size() && h[s[i]] <= h[stk.top()]) {
t.push_back(stk.top());
stk.pop();
}
stk.push(s[i]);
}
}
while (stk.size()) {
t.push_back(stk.top());
stk.pop();
}
printf("%s", t.c_str());
return 0;
}
```
#### 3、中缀表达式转后缀表达式(逻辑运算符+精简版本)
> **注**:因为逻辑运算,数字只有$0$和$1$,所以,`while`循环读取数字没用了,可以省略,当然,你非得要背模板写成一样的,也没有问题。
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
/*
中缀的逻辑表达式 转 后缀的逻辑表达式
测试用例:
0&(0|1|0)
答案:
001|0|&
*/
unordered_map<char, int> h{{'|', 1}, {'&', 2}};
string s;
string t;
stack<char> stk;
int main() {
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i]) || isalpha(s[i]))
t.push_back(s[i]);
else if (s[i] == '(')
stk.push(s[i]);
else if (s[i] == ')') {
while (stk.top() != '(') {
t.push_back(stk.top());
stk.pop();
}
stk.pop();
} else {
while (stk.size() && h[s[i]] <= h[stk.top()]) {
t.push_back(stk.top());
stk.pop();
}
stk.push(s[i]);
}
}
while (stk.size()) {
t.push_back(stk.top());
stk.pop();
}
printf("%s", t.c_str());
return 0;
}
```
#### 5、中缀表达式求值(四则版本)
```cpp {.line-numbers}
// OJ 测试:
// AcWing 3302. 表达式求值
// https://www.acwing.com/problem/content/3305/
#include <bits/stdc++.h>
using namespace std;
/*
中缀表达式求值
测试用例I:
(2+2)*(1+1)
答案:8
测试用例II:
2+(3*4)-((5*9-5)/8-4)
答案:13
*/
stack<int> num; //数字栈
stack<char> op; //操作符栈
//优先级表
unordered_map<char, int> h{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
/**
* 功能:计算两个数的和差积商
*/
void eval() {
int a = num.top(); //第二个操作数
num.pop();
int b = num.top(); //第一个操作数
num.pop();
char p = op.top(); //运算符
op.pop();
int r; //结果
//计算结果
if (p == '+')
r = b + a;
else if (p == '-')
r = b - a;
else if (p == '*')
r = b * a;
else if (p == '/')
r = b / a;
//结果入栈
num.push(r);
}
int main() {
//读入表达式
string s;
cin >> s;
//遍历字符串的每一位
for (int i = 0; i < s.size(); i++) {
//① 如果是数字,则入栈
if (isdigit(s[i])) {
//读出完整的数字
int x = 0;
while (i < s.size() && isdigit(s[i])) {
x = x * 10 + s[i] - '0';
i++;
}
i--; //加多了一位,需要减去
num.push(x); //数字入栈
}
//② 左括号无优先级,入栈
else if (s[i] == '(')
op.push(s[i]);
//③ 右括号时,需计算最近一对括号里面的值
else if (s[i] == ')') {
//从栈中向前找,一直找到左括号
while (op.top() != '(') eval(); //将左右括号之间的计算完,维护回栈里
//左括号出栈
op.pop();
} else { //④ 运算符
//如果待入栈运算符优先级低,则先计算
while (op.size() && h[op.top()] >= h[s[i]]) eval();
op.push(s[i]); //操作符入栈
}
}
while (op.size()) eval(); //⑤ 剩余的进行计算
printf("%d\n", num.top()); //输出结果
return 0;
}
```
#### 6、中缀表达式求值(逻辑表达式+拷贝四则版本)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
/*
0&(1|0)|(1|1|1&0)
答案:1
(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0
答案:0
*/
unordered_map<char, int> h{{'|', 1}, {'&', 2}};
stack<int> num;
stack<char> op;
void eval() {
int a = num.top();
num.pop();
int b = num.top();
num.pop();
char p = op.top();
op.pop();
int r;
if (p == '|')
r = b | a;
else if (p == '&')
r = b & a;
num.push(r);
}
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i])) {
int x = 0;
while (i < s.size() && isdigit(s[i])) {
x = x * 10 + s[i] - '0';
i++;
}
i--;
num.push(x);
} else if (s[i] == '(')
op.push(s[i]);
else if (s[i] == ')') {
while (op.top() != '(') eval();
op.pop();
} else {
while (op.size() && h[op.top()] >= h[s[i]]) eval();
op.push(s[i]);
}
}
while (op.size()) eval();
printf("%d\n", num.top());
return 0;
}
```
#### 7、中缀表达式求值(逻辑表达式+简化版本)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
/*
0&(1|0)|(1|1|1&0)
答案:1
(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0
答案:0
*/
unordered_map<char, int> h{{'|', 1}, {'&', 2}};
stack<int> num;
stack<char> op;
void eval() {
int a = num.top();
num.pop();
int b = num.top();
num.pop();
char p = op.top();
op.pop();
int r;
if (p == '|')
r = b | a;
else if (p == '&')
r = b & a;
num.push(r);
}
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i]))
num.push(s[i] - '0');
else if (s[i] == '(')
op.push(s[i]);
else if (s[i] == ')') {
while (op.top() != '(') eval();
op.pop();
} else {
while (op.size() && h[op.top()] >= h[s[i]]) eval();
op.push(s[i]);
}
}
while (op.size()) eval();
printf("%d\n", num.top());
return 0;
}
```
铺垫的知识完成,现在开始分析本题:
* **中缀逻辑表达式求值**
* **记录短路次数**
#### 规律总结
用一个三元组来替换原版本放在栈里的$int$,即:
$Node(v,a,b)$,代表:当前数字值是$v$,已经计算过的$\&$短路次数是$a$,已经计算过的$|$短路次数是$b$
找一个思路相似的简单问题给大家看看:
![](http://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/2022/12/a1eadd854acc22e84a64cd31e8a4a139.jpg)
则有下面的递推式:
$\large (1,a_1,b_1) | (?,a_2,b_2) \Rightarrow (1,a_1,b_1+1) 发生了短路运算,后面的不再计算$
$\large (0,a_1,b_1) | (?,a_2,b_2) \Rightarrow (?,a_1+a_2,b_1+b_2) 没有发生短路计算$
$\large (1,a_1,b_1) \& (?,a_2,b_2) \Rightarrow (?,a_1+a_2,b_1+b_2) 没有发生短路计算$
$\large (0,a_1,b_1) \& (?,a_2,b_2) \Rightarrow (0,a_1+1,b_1) 发生了短路运算,后面的不再计算$
**实现代码**:
```cpp {.line-numbers}
/*
测试用例1:
0&(1|0)|(1|1|1&0)
答案:
1
1 2
测试用例2:
(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0
答案:
0
2 3
*/
#include <bits/stdc++.h>
using namespace std;
struct Node {
int v, a, b; // v:代表当前的结果值,a: &短路的次数 b:|短路的次数
};
stack<Node> num;
stack<char> stk;
unordered_map<char, int> h{{'|', 1}, {'&', 2}};
void eval() {
// 这里要注意从栈中弹出元素的顺序,先出来的是y右子树,后出来的是x左子树
Node y = num.top();
num.pop();
Node x = num.top();
num.pop();
char p = stk.top();
stk.pop();
Node r;
if (p == '|') {
if (x.v == 1)
r = {x.v, x.a, x.b + 1};
else
r = {y.v, x.a + y.a, x.b + y.b};
} else {
if (x.v == 1)
r = {y.v, x.a + y.a, x.b + y.b};
else
r = {x.v, x.a + 1, x.b};
}
num.push(r);
}
int main() {
string s;
cin >> s;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i])) {
int x = 0;
while (i < s.size() && isdigit(s[i])) {
x = x * 10 + s[i] - '0';
i++;
}
i--;
num.push({x, 0, 0});
} else if (s[i] == '(')
stk.push(s[i]);
else if (s[i] == ')') {
while (stk.top() != '(') eval();
stk.pop();
} else {
while (stk.size() && h[stk.top()] >= h[s[i]]) eval();
stk.push(s[i]);
}
}
while (stk.size()) eval();
printf("%d\n", num.top().v);
printf("%d %d\n", num.top().a, num.top().b);
return 0;
}
```

View File

@@ -0,0 +1,59 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, w[N], l[N], r[N], si[N];
int getSize(int u) {
if (!u) return 0;
if (si[u]) return si[u];
si[u] = 1 + getSize(l[u]) + getSize(r[u]);
return si[u];
}
bool check(int u, int v) {
if (u == 0 && v == 0)
return true; // 两个空结点
else if (w[u] != w[v])
return false; // 权值不相等
else if (si[u] != si[v])
return false; // 树的大小不同
// u树左子树和v右子树相同 并且u树右子树和v左子树相同
else
return check(l[u], r[v]) && check(r[u], l[v]);
}
int dfs(int u) {
if (!u) return 0; // u树为空
int ans = 0;
// u树的左右子树对称
if (check(l[u], r[u]))
ans = si[u];
else
ans = max(dfs(l[u]), dfs(r[u]));
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> w[i];
for (int i = 1; i <= n; i++) {
int u, v;
cin >> u >> v;
// 为了方便处理数组下表,如果左右儿子为空,则为0
if (~u) l[i] = u;
if (~v) r[i] = v;
}
// 递归算出树中以每个节点为根的子树大小
getSize(1);
cout << dfs(1) << endl;
return 0;
}