luogu-P7073-后缀树

13 篇文章 0 订阅
10 篇文章 0 订阅

题目连接

  • 该题是CSP-J2-2020-T3

题目大意

输入一个后缀表达式,根据输入的条件,取反和输出。


  • 感谢MZH同学详细的题解

解法分析

  • 30 30 30

暴力。每次更改某一点数值后,重新计算后缀表达式。后缀表达式求值可以用一个栈。如果当前考虑的是数,就直接把它放进栈里;如果是!,就弹出一个数,把它的取反后的数放回栈;如果是|或者&,那么就弹出两个数,计算他们两个通过该种运算后得到的结果后放回栈里。最后栈里剩下的数即为答案。时间复杂度 O ( n 2 ) O(n^2) O(n2)

(讲真栈的想到了还想不到正解就怪了……)

  • 100 100 100

我们可以想到,在计算表达式的时候,可能会发现:有的时候,只要某个括号内的值为某一种结果后,答案就不可能变了。 而且我们发现这个括号结构、运算符以及代数像极了一棵树、树上的节点和叶子。这启发我们建树,找到这些不会影响结果的更改。

具体的,我们令运算符为非叶子节点,代数为叶子节点。比如在一开始我们可以直接将一开始的表达式转换成一棵二叉树。具体方法如同 30 30 30 分的栈。如果当前考虑的是数,就直接把它放进栈里,作为叶子;如果是!,就弹出一个节点,将!这个节点和弹出的节点连边,然后把这个节点上的数和把它的取反后的数放回栈;对|或者&也是同理。

我们不需要对这棵树求值。由于我们每次只能更改一个点,所以说对于一个 “或运算“ 或者 “与运算”,有的时候不会产生变化(如对1|1=1,改掉哪一个数都不影响结果)。所以可以用如下思路找到所有会导致表达式值变化的点:

  1. 考虑树上的点x
  2. 如果改变某一边会改变该点的结果,就递归到那个点;
  3. 如果到了叶子,说明改变这个点会一路向上改变到根,也就是总的表达式的结果。将这个点标记为会改变答案的点。

由于就两种运算,我们很容易想出什么时候改变某一边就会改变结果的方法。

整个复杂度为 O ( n ) O(n) O(n)

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
namespace ztd{
    using namespace std;
    typedef long long ll;
    template<typename T> inline T read(T& t) {
        t=0;short f=1;char ch=getchar();
        while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
        while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
        t*=f; return t;
    }
}
using namespace ztd;
const int maxn = 1e6+7;
char inp[20];
int n, m, qq;

inline int trans(char *ch, int i){ //将字符数组转化成数字,i是开始的下标,原理类似快读
    int t = 0, len = strlen(ch); 
    for(; i < len; ++i) t = t*10+(ch[i]-'0');
    return t;
}

int st[maxn], top;
int son[maxn][2], val[maxn];//son为树每个节点的两个儿子,val为每个点的初始节点
int to[maxn], a[maxn];
int ans[maxn];
int dfs(int x){//获取后缀表达式树上的每一个节点。
    if(a[x] >= 0) val[x] = a[x];
    else if(a[x] == -1) val[x] = !(dfs(son[x][0]));
    else if(a[x] == -2) val[x] = (dfs(son[x][0]) | dfs(son[x][1]));
    else if(a[x] == -3) val[x] = (dfs(son[x][0]) & dfs(son[x][1]));
    return val[x];
}
void dfs2(int x){
    if(a[x] >= 0){
        ans[x] = 1;
    }else if(a[x] == -1){
        dfs2(son[x][0]);
    }else if(a[x] == -2){ // |
        if(val[son[x][0]] == 1 && val[son[x][1]] == 0) dfs2(son[x][0]);
        else if(val[son[x][0]] == 0 && val[son[x][1]] == 1) dfs2(son[x][1]);
        else if(val[son[x][0]] == 0 && val[son[x][1]] == 0) dfs2(son[x][0]), dfs2(son[x][1]); 
    }else{// &
        if(val[son[x][0]] == 1 && val[son[x][1]] == 0) dfs2(son[x][1]);
        else if(val[son[x][0]] == 0 && val[son[x][1]] == 1) dfs2(son[x][0]);
        else if(val[son[x][0]] == 1 && val[son[x][1]] == 1) dfs2(son[x][0]), dfs2(son[x][1]); 
    }
}
int main(){
    int o;
    for(int i = 1; i; ++i){//漫长的读入
        scanf("%s", inp);
        if(isdigit(inp[0])){
            //如果输入到了一个第一位为数字的东西,那么我们输入到了n,求出n,并且停止如此的输入
            n = trans(inp,0);
            break;
        }else if(inp[0] == 'x'){
            //如果第一个字符为x,那么就是代数,也就是叶子。直接丢进栈里
            int tmp = trans(inp, 1);
            st[++top] = i;
            to[tmp] = i;
        }else if(inp[0] == '!'){
            //如果是!,那么弹出一个节点,将其作为!的左儿子,再把!放进栈里
            int ls = st[top--];
            son[i][0] = ls;
            a[i] = -1;//我们令a[i]=-1表示!,a[i]=-2为|,a[i]=-3为&
            st[++top] = i;
        }else if(inp[0] == '|' || inp[0] == '&'){
            //否则,弹出两个节点,算出他们的结果,放进栈里
            int rs = st[top--];
            int ls = st[top--];//ls,rs表示left son, right son,左、右儿子。
            son[i][0] = ls, son[i][1] = rs;
            if(inp[0] == '|') a[i] = -2;
            else if(inp[0] == '&') a[i] = -3;
            st[++top] = i;
        }
        m = i;
    }
    for(int i = 1; i <= n; ++i){
        read(o);
        a[to[i]] = o; 
    }
    dfs(m);
    dfs2(m);
    read(qq);
    while(qq--){
        read(o);
        printf("%d\n", val[m]^ans[to[o]]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值