题目连接
- 该题是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
,改掉哪一个数都不影响结果)。所以可以用如下思路找到所有会导致表达式值变化的点:
- 考虑树上的点
x
; - 如果改变某一边会改变该点的结果,就递归到那个点;
- 如果到了叶子,说明改变这个点会一路向上改变到根,也就是总的表达式的结果。将这个点标记为会改变答案的点。
由于就两种运算,我们很容易想出什么时候改变某一边就会改变结果的方法。
整个复杂度为 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;
}