洛谷 P8815 题解

洛谷 P8815 题解洛谷 P8815 题解 沿用某一篇题解 自己再解释一遍 方便巩固理解 参考 参考题解 题目描述 CSP J 2022 逻辑表达式 题目描述 逻辑表达式是计算机科学中的重要概念和工具 包含逻辑值 逻辑运算 逻辑运算优先级等内容 在一个逻辑表达式中

大家好,我是讯享网,很高兴认识大家。

洛谷 P8815 题解

沿用某一篇题解,自己再解释一遍,方便巩固理解。

参考:参考题解


题目描述:

[CSP-J 2022] 逻辑表达式

题目描述

逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。

在一个逻辑表达式中,元素的值只有两种可能: 0 0 0(表示假)和 1 1 1(表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为 &)和“或”(符号为 |)。其运算规则如下:

0 & 0 = 0 & 1 = 1 & 0 = 0 0 \mathbin{\&} 0 = 0 \mathbin{\&} 1 = 1 \mathbin{\&} 0 = 0 0&0=0&1=1&0=0 1 & 1 = 1 1 \mathbin{\&} 1 = 1 1&1=1
0 ∣ 0 = 0 0 \mathbin{|} 0 = 0 00=0 0 ∣ 1 = 1 ∣ 0 = 1 ∣ 1 = 1 0 \mathbin{|} 1 = 1 \mathbin{|} 0 = 1 \mathbin{|} 1 = 1 01=10=11=1

在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。

比如,表达式 0|1&0 的运算顺序等同于 0|(1&0);表达式 0&1&0|1 的运算顺序等同于 ((0&1)&0)|1

此外,在 C++ 等语言的有些编译器中,对逻辑表达式的计算会采用一种“短路”的策略:在形如 a&b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 0 a = 0 a=0,那么整个逻辑表达式的值就一定为 0 0 0,故无需再计算 b 部分的值;同理,在形如 a|b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 1 a = 1 a=1,那么整个逻辑表达式的值就一定为 1 1 1,无需再计算 b 部分的值。

现在给你一个逻辑表达式,你需要计算出它的值,并且统计出在计算过程中,两种类型的“短路”各出现了多少次。需要注意的是,如果某处“短路”包含在更外层被“短路”的部分内则不被统计,如表达式 1|(0&1) 中,尽管 0&1 是一处“短路”,但由于外层的 1|(0&1) 本身就是一处“短路”,无需再计算 0&1 部分的值,因此不应当把这里的 0&1 计入一处“短路”。

输入格式

输入共一行,一个非空字符串 s s s 表示待计算的逻辑表达式。

输出格式

输出共两行,第一行输出一个字符 01,表示这个逻辑表达式的值;第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 a&ba|b 的“短路”各出现了多少次。

样例 #1

样例输入 #1

0&(1|0)|(1|1|1&0) 

讯享网

样例输出 #1

讯享网1 1 2 

样例 #2

样例输入 #2

(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0 

样例输出 #2

讯享网0 2 3 

提示

【样例解释 #1】

该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:

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.inexpr/expr3.ans

【样例 #4】

见附件中的 expr/expr4.inexpr/expr4.ans

【数据范围】

∣ s ∣ \lvert s \rvert s 为字符串 s s s 的长度。

对于所有数据, 1 ≤ ∣ s ∣ ≤ 10 6 1 \le \lvert s \rvert \le {10}^6 1s106。保证 s s s 中仅含有字符 01&|() 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 s s s
中没有重复的括号嵌套(即没有形如 ((a)) 形式的子串,其中 a 是符合规范的逻辑表
达式)。

测试点编号 ∣ s ∣ ≤ \lvert s \rvert \le s 特殊条件
1 ∼ 2 1 \sim 2 12 3 3 3
3 ∼ 4 3 \sim 4 34 5 5 5
5 5 5 2000 2000 2000 1
6 6 6 2000 2000 2000 2
7 7 7 2000 2000 2000 3
8 ∼ 10 8 \sim 10 810 2000 2000 2000
11 ∼ 12 11 \sim 12 1112 10 6 {10}^6 106 1
13 ∼ 14 13 \sim 14 1314 10 6 {10}^6 106 2
15 ∼ 17 15 \sim 17 1517 10 6 {10}^6 106 3
18 ∼ 20 18 \sim 20 1820 10 6 {10}^6 106

其中:
特殊性质 1 为:保证 s s s 中没有字符 &
特殊性质 2 为:保证 s s s 中没有字符 |
特殊性质 3 为:保证 s s s 中没有字符 ()

【提示】

以下给出一个“符合规范的逻辑表达式”的形式化定义:

  • 字符串 01 是符合规范的;
  • 如果字符串 s 是符合规范的,且 s 不是形如 (t) 的字符串(其中 t 是符合规范的),那么字符串 (s) 也是符合规范的;
  • 如果字符串 ab 均是符合规范的,那么字符串 a&ba|b 均是符合规范的;
  • 所有符合规范的逻辑表达式均可由以上方法生成。

题解:表达式树是肯定可以做的, 这里主要解释为什么顺序做是可以的。

重点在于明晰模拟过程。

本文中“同级别”结构,“平层”结构是指仅由|&01组成的字符串,不包含()

我们先观察这样一个“同级别”的结构(不包含所谓“深层”的括号):

(0|1|1&1&0|0&1)


讯享网

这个结构中,有多个"|“与”&“并列存在没有括号,请注意它的运算顺序,不是先把所有的”&“算完,然后再进行”|",而是先算表达式树的前半部分,再算表达式树的后半部分。题目里是这么说的:

在形如a&b的逻辑表达式中,会先计算a部分的值

表达式 0&1&0|1 的运算顺序等同于 ((0&1)&0)|1

对于我们刚刚的例子,我们先用括号明确运算顺序:

( ((0|1)|((1&1)&0))|(0&1) )

我们在加括号的时候,先把&|区分开来,然后按照从左至右的顺序加括号。但是我们加完发现,在最后一个|运算中, 先运算先半部分括号,再运算后半部分括号,这是如果前半部分算出来是1,那么后半部分的&就不用关心了。(其中的差异就在|后面的&上,虽然它先与整体的|计算,但是在题意的短路策略中,如果前半部分是1,后半部分的&就不用算了)

因此我们发现,对于任何一个同级的|的并列,只要出现一次1,后面就全是1了,中间夹杂着&也没关系,例如:

0|1|(1|0&1)|0&0|0|1&1&1|0

上面的例子中,从第2个1开始,就全是短路了,并且每遇到一次|短路次数就+1。可以顺序做,是因为把括号补全后,同级的靠前的|是优先的。

如果遇到了0和&那么就短路。显然只有在第一个|短路之前的&短路才会被统计。

如此分析,在“短路”的意义下,|&是“同级别”的。

我们考虑如何计算表达式结果。我们发现不论是|短路还是&短路,我们都关心他前面的值(前面的计算结果、沿用的短路值或者运算符前面贴身的值)是否会造成短路,我们需要一个记录val的值。

这里我们还有一个十分重要的性质,我们考察运算不短路的情况:

0 | x == x

1 & x == x

我们发现,在平层结构中,对于一个运算符,它要么短路,要么对后面值的计算完全没有影响。

所以如果某个短路“中断”或者“没有”,有且只有两种情况:

1.在平层结构中,&的短路有可能被|截断,类似于结构&&&&x|中,我们顺序做的时候x仍在短路中,并没有被|截断,这个时候沿用val 的值即可(|不会中断,会一直1到最后)。

2.平层结构中,没有短路的时候,比如结构0|x&以及1&x|中,那么可以发现前半部分没有短路的结构对于式子结果没有影响, 式子结果只取决于x(无关运算顺序),因此把前半部分结果丢掉,用val记录x的值即可。

3.跳出括号的时候,短路被截断,而没有新的值,因此沿用旧的val继续运算。

最后,如果短路的时候遇到了括号,那就跳到该括号结尾。中间只需要对左右括号进行加减计数判断即可。

实际上val记录的就是答案,前半部分的(做过剪枝处理的)答案,因为无论是&还是|关心的都是前面的信息。后面的信息处理的时候,要么是这个运算符进入短路状态了,要么是这个运算符被扔掉了。

因此的处理策略就是:

1.正在短路中,那么跳括号,或者继续|短路,或者继续&短路,或者停止&短路,或者从括号跳出短路

2.如果没有短路,那么摄取新的val,判断新的短路。

总结一下, 本题之所以能够从树状结构变成顺序化,是因为短路的策略是顺序化的。只用根据前面处理是否短路。如果短路了,后面直接跳过;如果没断路,直接把这个运算符扔了,再继续做。短路的判断和处理都是顺序化的,因此算法也是顺序化的。

逆推一下,可能是先明确“短路”的运算顺序,手写观察和感受到顺序性的性质,再研究不短路的情况,然后细化细节就行了。

上代码:


讯享网#include <bits/stdc++.h> using namespace std; string s; int main() { 
    cin >> s; int len = s.size(); int off = 0,val = 0,ans1 = 0,ans2 = 0; for(int i =0;i < len;i++) { 
    if(off != 0) { 
    if(s[i] == '(') { 
    int cnt = 1; while(cnt && i < len) { 
    i++; if(s[i] == '(') cnt++; if(s[i] == ')') cnt--; } } else if(off == 2 && s[i] == '|') ans2++; else if(off == 1 && s[i] == '&') ans1++; else if(off == 1 && s[i] == '|') off = 0; else if(s[i] == ')') off = 0; } else { 
    if(s[i] == '0') val = 0; else if(s[i] == '1') val = 1; else if(s[i] == '|' && val == 1) { 
    ans2++; off = 2; } else if(s[i] == '&' && val == 0) { 
    ans1++; off = 1; } } } cout << val << "\n" << ans1 << " " << ans2; return 0; } 
小讯
上一篇 2025-04-09 11:40
下一篇 2025-01-18 19:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/59184.html