C++题解:[CSP-J2020]表达式

      

目录

题目 

题解:


题目 

小 C 热衷于学习数理逻辑。

有一天,他发现了一种特别的逻辑表达式。

在这种逻辑表达式中,所有操作数都是变量,且它们的取值只能为 0 或 1,运算从左往右进行。

如果表达式中有括号,则先计算括号内的子表达式的值。

特别的,这种表达式有且仅有以下几种运算:

  1. 与运算:𝑎 & 𝑏。当且仅当 𝑎 和 𝑏 的值都为 1 时,该表达式的值为 1。其余情况该表达式的值为 00。
  2. 或运算:𝑎 | 𝑏。当且仅当 𝑎 和 𝑏 的值都为 0 时,该表达式的值为 0。其余情况该表达式的值为 1。
  3. 取反运算:!𝑎。当且仅当 𝑎 的值为 0 时,该表达式的值为 1。其余情况该表达式的值为 0。

小 C 想知道,给定一个逻辑表达式和其中每一个操作数的初始取值后,再取反某一个操作数的值时,原表达式的值为多少。

为了化简对表达式的处理,我们有如下约定:表达式将采用后缀表达式的方式输入。

后缀表达式的定义如下:

  1. 如果 𝐸 是一个操作数,则 E𝐸 的后缀表达式是它本身。
  2. 如果 𝐸 是 𝐸1 𝑜𝑝 𝐸2 形式的表达式,其中 𝑜𝑝 是任何二元操作符,且优先级不高于 𝐸1、𝐸2 中括号外的操作符,则 E𝐸 的后缀式为 𝐸1′ 𝐸2′ 𝑜𝑝,其中 𝐸1′、𝐸2′ 分别为 𝐸1、𝐸2 的后缀式。
  3. 如果 𝐸 是 (𝐸1) 形式的表达式,则 𝐸1 的后缀式就是 𝐸 的后缀式。

同时为了方便,输入中:

a) 与运算符(&)、或运算符(|)、取反运算符(!)的左右均有一个空格,但表达式末尾没有空格
b) 操作数由小写字母 x 与一个正整数拼接而成,正整数表示这个变量的下标。例如:x10,表示下标为 10 的变量 𝑥10。

数据保证每个变量在表达式中出现恰好一次

输入格式

第一行包含一个字符串 𝑠,表示上文描述的表达式。

第二行包含一个正整数 𝑛,表示表达式中变量的数量。表达式中变量的下标为 1,2,…,𝑛。

第三行包含 𝑛 个整数,第 𝑖 个整数表示变量 𝑥𝑖 的初值。

第四行包含一个正整数 𝑞,表示询问的个数。

接下来 𝑞 行,每行一个正整数,表示需要取反的变量的下标。

注意,每一个询问的修改都是临时的,即之前询问中的修改不会对后续的询问造成影响。

数据保证输入的表达式合法。

变量的初值为 0 或 1。

输出格式

输出一共有 𝑞 行,每行一个 0 或 1,表示该询问下表达式的值。

数据范围

对于 20% 的数据,表达式中有且仅有与运算(&)或者或运算(|)。
对于另外 30% 的数据,|𝑠|≤1000,𝑞≤1000,𝑛≤1000。
对于另外 20% 的数据,变量的初值全为 0 或全为 1。
对于 100% 的数据,1≤|𝑠|≤1×106,1≤𝑞≤1×105,2≤𝑛≤1×105。
其中,|𝑠| 表示字符串 𝑠 的长度。

输入样例1:

x1 x2 & x3 |
3
1 0 1
3
1
2
3

输出样例1:

1
1
0

样例1解释

该后缀表达式的中缀表达式形式为 (x1 & x2) | x3。

对于第一次询问,将 x1𝑥1 的值取反。此时,三个操作数对应的赋值依次为 0,0,1。原表达式的值为 (0 & 0) | 1=1。

对于第二次询问,将 x2𝑥2 的值取反。此时,三个操作数对应的赋值依次为 1,1,1。原表达式的值为 (1 & 1) | 1=1。

对于第三次询问,将 x3𝑥3 的值取反。此时,三个操作数对应的赋值依次为 1,0,0。原表达式的值为 (1 & 0) | 0=0。

输入样例2:

x1 ! x2 x4 | x3 x5 ! & & ! &
5
0 1 0 1 1
3
1
3
5

输出样例2:

0
1
1

样例2解释

该表达式的中缀表达式形式为 (!x1) & (!((x2 | x4) & (x3 & (!x5))))。






题解:

知识点:表达式求值、链式前向星

分析:

由题意得,题目的表达式是后缀表达式,所以可以用栈建立一棵表达式树,然后从根结点向下递归求出表达式树的值。

因为计算过程只会出现逻辑运算,所以就可以利用'&'、'|'运算的短路性质,预处理出每个结点u的左右儿子发生变化时,是否影响到了最终的计算结果,即表达式的值。这样,每次询问就能在O(1)的时间复杂度内返回答案。

预处理出每个结点u的左右儿子发生变化时,我们可以对根结点的影响可以使用两次DFS:

  • 第一次DFS求出所有非叶子结点u的值w[u]和表达式的值
  • 第二次DFS求所有非叶子结点的值发生变化时能够影响到根结点的值:
    • 如果当前运算符是'!',子结点发生变化,就能影响到根结点的值
    • 如果当前运算符是'&',两个子结点分别为ab,如果w[a]1b结点发生变化,就能影响到根结点的值;如果w[b]1a结点发生变化,就能影响到根结点的值;
    • 如果当前运算符是'|',两个子结点分别为ab,如果w[a]0b结点发生变化,就能影响到根结点的值;如果w[b]0a结点发生变化,就能影响到根结点的值

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<bitset>
#include<string>
#include<cctype>
using namespace std;
const int N=1000010;
int n,m,h[N],e[N],ne[N],idx,w[N];//n是叶节点数量,m是结点总数量
bool st[N];//标记某节点是否会影响答案
char c[N];//存每个节点符号,下标从n开始,1~n不存是为了方便表示叶节点
stack<int> stk;//存每个节点的编号
inline void c_plus(){//提速
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
}
inline void init(){//预处理链式前向星
    memset(h,-1,sizeof(h));
    idx=0;
}
inline void add(int a,int b){//链式前向星,添加一条边a->b
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int dfs1(int u){//求原表达式的值,u为当前节点编号
    if (!c[u]){//如果该位置无符号(即如果是叶节点)
        return w[u];//直接返回其权值
    }else if (c[u]=='!'){//如果是非运算
        w[u]=!dfs1(e[h[u]]);//w[u]=将u递归求值后取反的值
    }else{//处理二元运算符
        int a=e[h[u]],b=e[ne[h[u]]];//a为左子节点,b为右子节点
        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]=1;
    if (!c[u]){//如果该位置无符号(即如果是叶节点)
        return;//无法继续下去
    }else if (c[u]=='!'){//如果是非运算
        dfs2(e[h[u]]);分析中已解释
    }else{//处理二元运算符
        int a=e[h[u]],b=e[ne[h[u]]];//a为其左子节点,b为右子节点
        if (c[u]=='&'){//如果是与运算
            if (w[a]){//分析中已解释
                dfs2(b);
            }
            if (w[b]){//分析中已解释
                dfs2(a);
            }
        }else{//如果是或运算
            if (!w[a]){//分析中已解释
                dfs2(b);
            }
            if (!w[b]){//分析中已解释
                dfs2(a);
            }
        }
    }
}
int main(){
    c_plus();
    init();
    string s;
    getline(cin,s);//因为字符串中有空格,所以用getline
    cin>>n;
    for (int i=1;i<=n;i++){
        cin>>w[i];
    }
    m=n;//m=n+非叶节点数量,所以让m先等于n,再加上非叶节点数量
    for (int i=0;i<s.size();i++){
        if (s[i]==' '){
            continue;
        }else if (s[i]=='x'){//求出节点编号
            int j=i+1,x=0;
            while (j<s.size() && isdigit(s[j])){//双指针算法求整个数字
                x=x*10+s[j++]-'0';
            }
            stk.push(x);//将该数字存入栈
            i=j;//注意不是i=j-1,毕竟由题意此时j表示的是空格
        }else if (s[i]=='!'){//如果是非运算
            c[++m]=s[i];
            add(m,stk.top());//表示'!'的子节点是栈顶元素
            stk.pop();
            stk.push(m);//将当前子节点编号放进栈
        }else{//处理二元运算符'&'和'|'
            c[++m]=s[i];
            add(m,stk.top());
            stk.pop();
            add(m,stk.top());
            stk.pop();
            stk.push(m);
        }
    }
    int ans=dfs1(m);
    dfs2(m);
    int q,x;
    cin>>q;
    while (q--){
        cin>>x;
        if (st[x]){//若该节点会影响答案
            cout<<(ans^1)<<endl;//直接取反,因为答案不是0就是1
        }else{
            cout<<ans<<endl;//正常输出
        }
    }
    return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值