洛谷P8815 [CSP-J 2022] 逻辑表达式

这个问题是让用C++实现对这种具有括号和优先级(&>I)的表达式的解析和计算,平时我们计算的表达式形式可以被称作中缀表达式,因为运算符号在数字的中间,比如

3+5*2,(3+5)*2

将中缀表达式转化成后缀表达式后,可以构建表达式树,以树状的结构完成对表达式值的计算

后缀表达式的计算方法:建立一个栈,从左到右遍历后缀表达式中的元素,如果是数字,就将其加入栈,如果是运算符,从栈顶取两个数进行该运算符对应的运算,将运算结果加入栈,最后栈中剩下的唯一一个元素就是表达式的值

例如 3+5*2=13转化到后缀表达式:3 5 2 * +

遍历第一个到第三个元素后,栈的状态:3 5 2

遍历到*时,计算5*2=10(注意顺序:5在运算符左侧,2在运算符右侧,虽然当前情况交换顺序没有影响,但是在本题下图的树中有影响),栈变为 3 10

遍历到+,计算3+10=13,结果就是13

将中缀表达式转化为后缀表达式:

中缀表达式中,含有的元素可能有:运算符(具有优先级,比如*>+),括号(左括号和右括号),数字

建立用来辅助完成转换的栈sta(stack)和用来记录转换结果(后缀表达式)的栈suf(suffix),从左到右遍历中缀表达式,遍历到当前元素值为x时:

如果x是数字,将其加入suf

如果x是左括号,将其加入sta

如果x是右括号,将sta中的元素不断弹出,直到遇到左括号,将左括号也从sta中弹出

如果x是运算符,若栈顶元素运算符的优先级(栈中此时只有可能含运算符和左括号)大于等于x,将sta栈顶元素运算符加入suf,从栈中弹出,继续判断新栈顶元素运算符与x的关系,继续加入suf弹出sta,遇见左括号的话停止,这之后将x加入sta

从左到右遍历中缀表达式结束后,如果sta中还有元素,依次弹出到suf

现在的思路是:将中缀表达式转成后缀表达式,用上述介绍的方法求后缀表达式的值,但是,实际上,求后缀表达式值的时候所用顺序和刚才介绍的方法不同,求后缀表达式的值是先建立表达式树,然后再从根开始递归计算,因为这样可以利用短路减少运算

比如0 1 0 | & 1 1 | 1 0 & | | 

所以,统计的短路次数的时候要倒着进行dfs遍历树

注:这种计算中缀表达式值的问题,所用到的中缀表达式转后缀表达式,以及后缀表达式求值的方法暂且归结为特定算法,暂只熟悉算法,不讨论证明过程

#include<iostream>
#include<cstdio>
#include<stack>
#include<vector>
using namespace std;

const int maxn=1e6+5;

string s;
stack<char> sta;
vector<char> suf;
int cnt,ans1,ans2;

struct node{
    int l,r,v;
    node() : l(0), r(0), v(0) {}
    node(int x, int y, int z) : l(x), r(y), v(z) {}
}tree[maxn];

void build_tree(){
    stack<int> nodes;
    for(int i=0;i<suf.size();i++){
        if(suf[i]=='0' || suf[i]=='1'){
            tree[++cnt]=node(-1,-1,suf[i]-'0');
            nodes.push(cnt);
        }
        else if(suf[i]=='&'){
            int rs=nodes.top();nodes.pop();
            int ls=nodes.top();nodes.pop();
            int v;
            tree[++cnt]=node(ls,rs,2);
            nodes.push(cnt);
        }else if(suf[i]=='|'){
            int rs=nodes.top();nodes.pop();
            int ls=nodes.top();nodes.pop();
            tree[++cnt]=node(ls,rs,3);
            nodes.push(cnt);
        }
    }

}

int dfs(int u){
    if(tree[u].v==0 || tree[u].v==1) return tree[u].v;
    if(tree[u].v==2){       //&
        int ls=tree[u].l,rs=tree[u].r;
        if(dfs(ls)==0){
            ans1++;
            return 0;
        }else {
            return dfs(rs);
        }
    }else {
        int ls=tree[u].l,rs=tree[u].r;
        if(dfs(ls)==1){
            ans2++;
            return 1;
        }else {
            return dfs(rs);
        }
    }

}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);

    cin>>s;
    //中缀表达式转后缀表达式
    for(int i=0;i<s.size();i++){
        if(s[i]=='1' || s[i]=='0'){
            suf.push_back(s[i]);
        }else if(s[i]=='&'){
            while(!sta.empty() && sta.top()=='&'){
                suf.push_back('&');
                sta.pop();
            }
            sta.push('&');
        }else if(s[i]=='|'){
            while(!sta.empty() && sta.top()!='('){
                suf.push_back(sta.top());
                sta.pop();
            }
            sta.push('|');
        }else if(s[i]=='('){
            sta.push('(');
        }else if(s[i]==')'){
            while(sta.top()!='('){
                suf.push_back(sta.top());
                sta.pop();
            }
            sta.pop();
        }
    }
    while(sta.size()){
        suf.push_back(sta.top());
        sta.pop();
    }

    for(int i=0;i<suf.size();i++){
        cout<<suf[i]<<" ";
    }
    //建立表达式树
    build_tree();
    //dfs模拟求值
    cout<<dfs(cnt)<<"\n";
    cout<<ans1<<" "<<ans2<<"\n";
    return 0;
}

### P8815 题目解析及算法思路 #### 1. 题目概述 P8815 [CSP-J 2022] 逻辑表达式 是一道涉及后缀表达式计算的题目。题目要求对一个由数字、变量和逻辑运算符组成的后缀表达式进行求值,并输出最终结果。此题的核心在于如何高效地处理后缀表达式并正确实现逻辑运算。 #### 2. 算法设计 根据已知信息,解决该问题可以采用栈的数据结构来模拟后缀表达式的计算过程[^3]。具体实现步骤如下: - **栈的使用**:遇到数字或变量时直接入栈;遇到逻辑运算符时弹出栈顶两个元素进行运算,将结果重新压入栈中。 - **逻辑运算**:支持与(AND)、或(OR)、非(NOT)等基本逻辑操作,需确保运算顺序正确。 - **时间复杂度**:整个算法的时间复杂度为 \(O(n)\),其中 \(n\) 为表达式的长度,因为每个元素仅被访问一次。 #### 3. 实现代码示例 以下是基于上述算法设计的 Python 实现代码: ```python def evaluate_postfix(expression, variables): stack = [] for token in expression: if token.isdigit(): # 如果是数字,直接入栈 stack.append(int(token)) elif token.isalpha(): # 如果是变量,查找其值并入栈 stack.append(variables[token]) else: # 如果是运算符,弹出栈顶两个元素进行运算 if token == '&': # 与运算 b = stack.pop() a = stack.pop() stack.append(a & b) elif token == '|': # 或运算 b = stack.pop() a = stack.pop() stack.append(a | b) elif token == '!': # 非运算 a = stack.pop() stack.append(1 - a) # 非运算的结果为取反 return stack[0] # 示例输入 expression = ['a', 'b', '&', 'c', '|'] variables = {'a': 1, 'b': 0, 'c': 1} # 计算结果 result = evaluate_postfix(expression, variables) print(result) ``` #### 4. 注意事项 - 在处理变量时,需预先定义所有变量的值。 - 对于 NOT 运算符,需注意其只作用于单个操作数。 - 输入的后缀表达式格式必须正确,否则可能导致程序运行错误。 #### 5. 测试与验证 为了确保算法的正确性,可以通过以下测试用例进行验证: - 测试用例 1:`['1', '0', '&']`,预期输出为 `0`。 - 测试用例 2:`['1', '1', '|']`,预期输出为 `1`。 - 测试用例 3:`['1', '!', '0', '&']`,预期输出为 `0`。 ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值