CSP-J 2022_第三题逻辑表达式

题目

逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。
在一个逻辑表达式中,元素的值只有两种可能: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 计入一处“短路”。

输入格式
输入共一行,一个非空字符串 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】
该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:

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
【数据范围】

设 ∣s∣ 为字符串 s 的长度。

对于所有数据,1≤∣s∣≤10
6
。保证 s 中仅含有字符 0、1、&、|、(、) 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 s 中没有重复的括号嵌套(即没有形如 ((a)) 形式的子串,其中 a 是符合规范的逻辑表达式)。
在这里插入图片描述
其中:
特殊性质 1 为:保证 s 中没有字符 &。
特殊性质 2 为:保证 s 中没有字符 |。
特殊性质 3 为:保证 s 中没有字符 ( 和 )。

【提示】

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

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

代码

#include <bits/stdc++.h>//万能头文件,包含c++标准库中常用头文件 
using namespace std;//使用std命名空间,使用标准库中类型、函数时无需std::前缀 
int n_yu,n_huo; //与和或运算短路次数 
struct node{//二叉树节点类型 
    char x;//操作符号'&'、'|'、'('、')' 、'0'、'1' 
    bool k;//各逻辑运算结果 
    node *fa, *left, *right;//指向父、左、右节点的指针 
    node() : x('*'),fa(nullptr), left(nullptr), right(nullptr) {}//默认构造函数,初始化节点 
    node(char cx){//带参构造函数,根据字符初始化节点 
		x=cx,fa=nullptr, left=nullptr, right=nullptr;
		if(cx=='1'||cx=='0')k=cx-'0';
	}
};
class Tree{
	public:
	    node *root,*current;    // 根节点指针,当前操作节点指针(构建树时用)
		// 构造函数:初始化根节点和当前节点
	    Tree(){//构造函数,构造根节点为'r'(不变),当前节点指向根 
			root=new node('r'), current=root;
		}
	    ~Tree() {// 析构函数:递归释放整个语法树节点内存
	        clear(root);
	        root = current = nullptr;
	    }
	    void insert(char x) {//根据字符类型,插入并构建语法树     
	        node* newNode = new node(x);// 创建新节点
	        if(x=='0'||x=='1'||x=='('){//操作数和左括号 
	        	newNode->fa = current;//成为当前节点的子节点 
	        	if(!current->left)current->left = newNode;//当前节点左子节点空,就挂到左节点 
				else if(!current->right)current->right = newNode;//否则挂右节点 
			}else if(x==')'){//右括号时要归拢,调整当前位置回到左括号处 
				current=current->fa;//下句无法越过当前的左括号,添加该句主动升一层 
				while(current->x!='(')current=current->fa;//循环一直找到左括号位置 
				newNode->fa=current;current->right=newNode;//左括号节点的右节点是右括号 
				newNode=newNode->fa;//左括号调整为当前节点位置 
			}
			else if(x=='|'){//优先级在|和&下,当前位置升到紧邻左括号或者根前 
				while(current->fa->x!='('&&current->fa!=root)current=current->fa;
				current->fa->left=newNode;newNode->fa=current->fa;//|变成当前节点父节点的左子, 
				current->fa=newNode;newNode->left=current;//当前节点变成|的左子 
			}else if(x=='&'){//如果父节点是|,则&优先级高,要下潜。否则遇到&就得上浮 
				if(current->fa->x=='|'){
					current->fa->right=newNode;newNode->fa=current->fa;//当前节点的父节点变成&的父节点, 
					current->fa=newNode;newNode->left=current;//当前节点变成&节点的左节点 
				}else{
					while(current->fa->x=='&')current=current->fa;//父是&就得上浮,得父不是&得节点为当前节点 
					if(current->fa->left==current)current->fa->left=newNode;//当前节点是父节点的左子,挂父节点的左树 
					else current->fa->right=newNode;//挂父节点的右树 
					newNode->fa=current->fa;
					current->fa=newNode;newNode->left=current;//当前节点(&)挂插入节点的左树 
				}
			}
	        current=newNode;//插入的节点变成当前节点(括号选左括号) 
	    }
	    void clear(node* x) {// 递归清除节点及其子节点
	        if (!x) return;
	        clear(x->left);
	        clear(x->right);
	        delete x;
	    }
	    void view1(node *x){
	    	cout<<x->x;
	    	if(x->left)view1(x->left);
	    	if(x->right)view1(x->right);
		}
		void view3(node *x){
	    	if(x->left)view3(x->left);
	    	cout<<x->x;
	    	if(x->right)view3(x->right);
		}
		void view2(node *x){
	    	cout<<x->x<<"["<<x->k<<"] ";
	    	if(x->left)view2(x->left);
	    	if(x->right)view2(x->right);
		}
		bool run(node *n){
			bool k1,k2; //左右树运算结果 
			if(n->x=='r')return n->k=run(n->left);//根和左括号处结果仅源自左树 
			if(n->x=='(')return n->k=run(n->left);//右括号不影响结果 
			if(n->x=='0'||n->x=='1')return n->k;//运算数 
			if(n->x=='&'){//与运算 
				k1=run(n->left);//先算左树 
				if(k1==0){//如果左树是0,直接返回0,不用算右树 
					n_yu++;return n->k=0;
				}
				k2=run(n->right);
				return n->k=k1&&k2;
			}
			if(n->x=='|'){//或运算 
				k1=run(n->left);
				if(k1==1){//如果左树是1,直接返回1,不用算右树 
					n_huo++;return n->k=1;
				}
				k2=run(n->right);
				return n->k=k1||k2;
			}
		}
}tree;
int main(){
	//freopen("expr.in","r",stdin);
	string s;cin>>s;
	for(int i=0;s[i];i++){
		tree.insert(s[i]); 
		//cout<<i<<" "<<s[i]<<"先序";tree.view1(tree.root);cout<<endl;
	}
	//cout<<"先序";tree.view1(tree.root);cout<<endl;
	//cout<<"中序";tree.view3(tree.root);cout<<endl;
	cout<<tree.run(tree.root)<<endl;
	cout<<n_yu<<" "<<n_huo<<endl;
	//cout<<"先序";tree.view2(tree.root);cout<<endl;
	return 0;
}

思路

1.构建二叉树,n个字符n次操作,)、|和&需要上溯,最差情况是从当前节点回溯到根节点(n次),所以时间复杂度是o(n^2),这里n=10 ^6,结果是10 ^12,大于1秒的大约次数10 ^8次。有可能会超时——还好没有。
遍历逻辑表达式,每个字符建立一个指针空间节点
将所有节点连接成二叉树
经过一些样例数据的实操,得到逻辑关系
root
开始就有,用r标记,位置不变。后继只有左没有右
0、1、(
添(节点,或左或右
)
上溯,找到(节点,成为(节点的右节点,父(成当前节点。
先回溯一次,避免连续右括号时停留在当前(节点
|
添节点
上溯,直到父是根r或(,当前节点变成自己的左节点,自己成父的右节点
&
添节点
父是|,则当前节点(0、1)成为自己的左节点,自己成父(|)的右节点
父是&,则上溯直到父是|或者根或(,当前节点成左节点,自己成父的右节点
2.递归计算。run函数,,先左右再根,后序遍历,至多每个节点算一次O(n)。
return k=run(left)&&run(right);感觉只算左(不知道原因)
须要分别计算
k1=run(left)
k2=run(right)
&运算,左是0是短路。|运算左是1是短路。(当然另一边也存在这种情况,但是本题说了只是左)
有10^6个节点

作图验证

在这里插入图片描述

小结

冬麦需要9个月,夏玉米需要4个月。万事万物的生长成熟都需要足够的时间!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值