今天看 CSP-J 2022 的这道题,考点是模拟。
题目来源:ACGO A697 | 洛谷 P8815
难度定位:普及+/提高 | 通过率:47.94%
一、题目信息
题目名称:逻辑表达式
所属赛事:2022 年 CSP-J 组复赛
官方难度:普及+/提高
涉及知识点:模拟、栈
题目链接:https://www.acgo.cn/problemset/info/697
洛谷编号:P8815(同一道题,两个平台均可提交,代码通用)
易错点(很多人栽在这)
- 按题意逐步实现时,把每个操作拆成独立步骤,别把多个条件揉进一个式子
- 规则里的『首次满足』『优先最早』这类措辞要落实成具体的比较顺序
题意简述
题意其实很直白:
逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。
在一个逻辑表达式中,元素的值只有两种可能:0(表示假)和 1(表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为&)和“或”(符号为|)。其运算规则如下:
0&0 = 0 & 1 = 1 & 0 = 0, 1 & 1 = 1
0∣0=0,0 | 1 = 1 | 0 = 1 | 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计入一处“短路”。
前置知识
这题要用到的工具不多,但有一个容易忘,先过一遍。
按题意一步步走
模拟题不考算法,考的是「把话翻译成代码」的耐心。读题时把变量、循环、边界一条条列出来,再动手写,比边写边想快得多。
后进先出
栈的特点是最后放进去的最先拿出来。遇到「括号匹配」「表达式求值」「找最近的更大元素」这类题,先想栈。
二、思路分析
这题官方难度普及+/提高,前半场到中场比较稳妥。
思路的入口在哪里?
这道题大部分人貌似都是表达式求值的,不过其实可以有更简单的做法,直接从左到有一遍扫过去即可。
首先,你先想清楚在什么情况下一段表达式没有贡献。如果是 0& ,则在下一次出现 | 或和它前面 一个 (匹配的) 之后才会产生贡献,而前面一段则为 0。如果是 1|,则出现和它前面 一个 (匹配的) 之后才会产生贡献。
其次,我们要想清楚优先级怎么搞,但事实上,优先级并不会影响到结果,因为如果出现 1|,则会一直调到后面的括号,所以它的优先级一定高于 &,如果是 0|,那它并不会对结果产生影响,& 同理。如果不是很明白上面说的,你可以模拟一下数据:0&(1|0)|(1|1|1&0)
出现 0&,则一直跳到 0&(1|0)|,第一个 | 因为在括号内,所以可以忽略,此时变成 0|(1|1|1&0),因为 0|不会产生贡献,所以表达式可以变为 (1|1|1&0),直接一口气跳到最后的括号,于是就做完了。
三、完整代码
#include <iostream> #include <vector> #include <algorithm> using namespace std; // CSP-J 2022 逻辑表达式(A697) // 给定中缀逻辑表达式(含 & | ( )),求值并统计两种短路的次数。 // 做法:递归下降解析表达式,返回 (值, & 短路次数, | 短路次数)。 // - 先解析由 | 连接的若干 & 项;对 | :若左值为 1,则短路,右值不再求值 // - 再解析由 & 连接的若干原子;对 & :若左值为 0,则短路,右值不再求值 // 注意:被短路的部分里即使有短路也不计入。 struct Res { int val; long long cntAnd; long long cntOr; }; string s; int pos; Res parseOr(); // 前向声明 Res parseAtom() { if (s[pos] == '(') { ++pos; // 跳过 '(' Res r = parseOr(); ++pos; // 跳过 ')' return r; } int v = s[pos] - '0'; ++pos; Res r; r.val = v; r.cntAnd = 0; r.cntOr = 0; return r; } Res parseAnd() { Res cur = parseAtom(); while (pos < (int)s.size() && s[pos] == '&') { ++pos; if (cur.val == 0) { // 短路:右侧不再求值,但表达式结构仍要跳过 // 为了正确跳过一个原子(可能带括号),这里仍需要解析但不计短路 Res rhs = parseAtom(); (void)rhs; cur.cntAnd += 1; } else { Res rhs = parseAtom(); cur.val = cur.val & rhs.val; cur.cntAnd += rhs.cntAnd; cur.cntOr += rhs.cntOr; } } return cur; } Res parseOr() { Res cur = parseAnd(); while (pos < (int)s.size() && s[pos] == '|') { ++pos; if (cur.val == 1) { Res rhs = parseAnd(); (void)rhs; cur.cntOr += 1; } else { Res rhs = parseAnd(); cur.val = cur.val | rhs.val; cur.cntAnd += rhs.cntAnd; cur.cntOr += rhs.cntOr; } } return cur; } int main() { cin >> s; pos = 0; Res r = parseOr(); cout << r.val << '\n' << r.cntAnd << ' ' << r.cntOr << '\n'; return 0; }四、代码分段拆解
完整代码在上面,下面按功能分块解释。
头文件与全局变量
先把用到的头文件和全局变量放上来。全局变量在竞赛里很常用,好处是函数之间共享方便,代价是要小心初始化和被意外改写。
#include <iostream> #include <vector> #include <algorithm>using namespace std; struct Res { int val; long long cntAnd; long long cntOr; }; string s; int pos; int v = s[pos] - '0';主体逻辑
主体部分按顺序完成读入、计算、输出。main函数尽量保持线性,把复杂的逻辑拆成独立函数,出问题时更容易定位。
边界处理
这类题最容易在边界上翻车:最小值、最大值、只剩一个元素的情况,都在脑中过一遍。
五、实例模拟
光看代码容易虚,用样例实测一遍最踏实。
样例 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以第一组样例为例,输入是0&(1|0)|(1|1|1&0),正确输出是1、1 2。建议把这两行数据抄在纸上,用上面的代码手动走一遍,重点看循环变量怎么变化、答案在哪一步被更新。能自己推出来的东西,比看懂代码印象深得多。
六、踩坑总结
最后总结几个要点。
- 按题意逐步实现时,把每个操作拆成独立步骤,别把多个条件揉进一个式子
- 规则里的『首次满足』『优先最早』这类措辞要落实成具体的比较顺序
另外提醒一句:这题在 CSP-J 2022 里是普及+/提高难度,考场上如果没有把握拿满分,先把暴力写法交上去拿到部分分,再回头优化。CSP 是打分制,空着不写才是最亏的。
七、知识点拓展
这题的核心是模拟。
模拟题在 CSP 里属于「必须拿满」的部分。它的训练价值不在于算法,而在于把一段自然语言描述准确翻译成代码的能力。训练方法很简单:读完题先不写代码,用中文把步骤一步步列出来,再逐条翻译。
建议的练习路径:
- 先把本题独立写一遍,不看代码,遇到卡壳再看提示
- 换个平台提交(ACGO 和洛谷都试试),确保代码在两个平台都能过
- 找两到三道同类型的题练手,巩固这类问题的识别能力
- 本题还涉及 栈,建议一并复习
八、适用人群
这道题的适用面:
- 已经能写基础题,想在 CSP-J 里稳定拿到中档分的同学
- 学完基础 DP 或图论,需要真题检验掌握程度的同学
- 想弄明白模拟类问题标准解法的同学
代码有任何问题欢迎指正,我会及时更新。
关于这个系列
这是「CSP 真题逐题精讲」的第 30 篇。我会按年份从 2019 一路刷到 2025,
CSP-J 和 CSP-S 全覆盖。每篇都给:双平台题号(ACGO + 洛谷)+ 逐行代码拆解 + 真实样例模拟。
跟着刷完这一套,CSP 第一轮的选择题和基础题基本就稳了。
下一篇:CSP-S 2022 策略游戏(普及+/提高)
👉 关注我,不错过后续更新。评论区告诉我你现在刷到哪一年了,我看看能不能按大家的进度调整更新节奏。