表达式(CSP-J 2021-Expr)题目详解

先把题目从洛谷搬上来

题目描述

小 C 热衷于学习数理逻辑。有一天,他发现了一种特别的逻辑表达式。在这种逻辑表达式中,所有操作数都是变量,且它们的取值只能为 0 或 1,运算从左往右进行。如果表达式中有括号,则先计算括号内的子表达式的值。特别的,这种表达式有且仅有以下几种运算:

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

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

为了化简对表达式的处理,我们有如下约定:

表达式将采用后缀表达式的方式输入。

后缀表达式的定义如下:

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

同时为了方便,输入中:

  1. 与运算符(&)、或运算符(|)、取反运算符(!)的左右均有一个空格,但表达式末尾没有空格
  2. 操作数由小写字母 x 与一个正整数拼接而成,正整数表示这个变量的下标。例如:x10,表示下标为 10的变量 x10​。数据保证每个变量在表达式中出现恰好一次

输入格式

第一行包含一个字符串 s,表示上文描述的表达式。
第二行包含一个正整数 n,表示表达式中变量的数量。表达式中变量的下标为 1,2,⋯,n。
第三行包含 n 个整数,第 i 个整数表示变量 xi​ 的初值。
第四行包含一个正整数 q,表示询问的个数。
接下来 q 行,每行一个正整数,表示需要取反的变量的下标。注意,每一个询问的修改都是临时的,即之前询问中的修改不会对后续的询问造成影响。
数据保证输入的表达式合法。变量的初值为 0 或 1。

输出格式

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

输入输出样例

输入 #1复制

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

输出 #1复制

1
1
0


这道题属实有点难度,考场时直接过了,回过头来发现还是有技巧性的


题解

对于数据的储存,我采用一下形式

首先定义一个结构体:

struct node{
    char op;
    int value;
    int dp[2];
    vector<node*>son;
    node(){
        dp[0]=0;
        dp[1]=0;
        value=0;
        op='F';                //F表示没有,可以标记叶节点
        vector<node*>son();   //初始化,不加也行
    }
};

dp数组下文会解释,value存储该节点子树的值,一个vector数组表示它的子节点,容易发现,子节点的数量取决于该节点的符号,为!时只有一个;而为&或|时则为两个。叶节点都是储存的值,没有符号,而非叶节点初始状态下没有值,但有符号。

可以利用stack容器来处理后缀表达式

建立一个date数组来记录下标为x的node类型,与stack联系起来

我们采用指针的形式定义各种数据,因为这样我们能动态分配内存,减少占用量。但更为关键的是,这样可以使我们建立各种数据结构建立起联系,在某一值改变的同时,其相应指针的其他类型也随之改变。

对待这道题的核心算法有两种方法

第一种

        就是每次输入要改变的值,然后遍历一遍整个树,给出答案。但显然在查询次数较多的情况下,这种方法肯定会超时。

第二种

       就是在输入要改变的值之前,我们打表:

如何打表呢,首先我们要Dfs从下而上遍历整个树,求出每个节点的默认值。

让后我们再定义一个函数Dfs_dp从上而下遍历整个树,求出每个节点的dp值。

dp[]数组的含义为当这个节点的值为x,整个树的运算结果为dp[x],dp显然只有两个状态,所以我们定义成 int dp[2]就足够了。

自上而下,对于根节点,显然当它的dp值就是整个树的值。

对于每个子节点,我们考虑两种情况:

1.父节点对应的符号为!时,它的dp值就是其父节点相反的dp值。

2.父节点对应的符号为& 或 |时,首先求出当它的值为x时,与其父节点的另外一个子节点进行相应符号运算的结果z,然后对应其相应父节点的dp[z]的值

这样更加快速的原因在于我们遍历完一遍树就可以求出所有情况的值,相较于方案一,我们不用多次遍历。

最后,输入每个要改变的节点时,我们输出改变后结果的dp值即可。

AC code is here

#include<cstdio>
#include<stack>
#include<vector>
#include<cstring>
using namespace std;
struct node{
    char op;
    int value;
    int dp[2];
    vector<node*>son;
    node(){
        dp[0]=0;
        dp[1]=0;
        value=0;
        op='F';
        vector<node*>son();
    }
};
stack<node*>fuel;                    
node* date[100000];
char str[100000];
int tot=0,quenum,que;
using namespace std;
int Dfs_solve(node* x){
    if(x->op=='F'){
        return 0;
    }
    for(int i=0;i<x->son.size();i++){
        Dfs_solve(x->son[i]);
    }
    if(x->op=='!'){
        x->value=(!x->son[0]->value);
    }
    else if(x->op=='&'){
        x->value=(x->son[0]->value & x->son[1]->value); 
    }
    else if(x->op=='|'){
        x->value=(x->son[0]->value | x->son[1]->value); 
    }
    return 0;
}
void Dfs_dp(node* x,node* father){
    if(father==NULL){
        x->dp[0]=0;
        x->dp[1]=1;
    }
    else{
        if(father->op=='!'){
            x->dp[0]=father->dp[1];
            x->dp[1]=father->dp[0]; 
        }
        else if(father->op=='&' || father->op=='|'){
            node *other;
            if(father->son[0]==x){
                other=father->son[1];
            }
            else{
                other=father->son[0];
            }
            
            if(father->op=='&'){
                x->dp[0]=father->dp[0];
                x->dp[1]=father->dp[other->value]; 
            }
            else{
                x->dp[1]=father->dp[1];
                x->dp[0]=father->dp[other->value];
            }
        }
    }
    for(int i=0;i<x->son.size();i++){
        Dfs_dp(x->son[i],x);
    }
    return;
}
int main(){
    while(scanf("%s",str)){
        if(str[0]=='x'){
            int num=0;
            for(int i=1;i<strlen(str);i++){
                num=(num<<3)+(num<<1)+(str[i]^48);
            }
            date[num]=new node;
            fuel.push(date[num]);
        }
        else if(str[0]=='&' || str[0]=='|'){
            node* a;
            node* b;
            node* c=new node;
            a=fuel.top();
            fuel.pop();
            b=fuel.top();
            fuel.pop();
            c->op=str[0];
            c->son.push_back(a);
            c->son.push_back(b);
            fuel.push(c);
        }
        else if(str[0]=='!'){
            node* a;
            node* b=new node;
            a=fuel.top();
            fuel.pop();
            b->son.push_back(a);
            b->op='!';
            fuel.push(b);
        }
        else{
            for(int i=0;i<strlen(str);i++){
                tot=(tot<<3)+(tot<<1)+(str[i]^48);
            }
            for(int i=1;i<=tot;i++){
                scanf("%d",&date[i]->value);
            }
            break;
        }
    }
    node* root=fuel.top();
    Dfs_solve(root);
    Dfs_dp(root,NULL);
    scanf("%d",&quenum);
    for(int i=0;i<quenum;i++){
        scanf("%d",&que);
        printf("%d\n",date[que]->dp[!date[que]->value]);
    }
    return 0;
}

END~~~

写作不易,若有不懂的地方欢迎评论,我会立刻回复。

谢谢大家的支持

上一篇:iOS后台如何保持socket长连接和数据传输


下一篇:SharePoint Online PnPjs 批量更新项目