信息学奥赛一本通 2006:【20CSPJ普及组】表达式 | 洛谷 P7073 [CSP-J2020] 表达式

【题目链接】

ybt 2006:【20CSPJ普及组】表达式
洛谷 P7073 [CSP-J2020] 表达式

【题目考点】

  1. 后缀表达式
  2. 表达式树

通过后缀表达式建立表达式树:

  • 遍历后缀表达式字符串:
    • 若读取到数字,生成结点,入栈
    • 若读取到运算符,生成运算符结点,出栈两个结点,分别作为运算符结点的左右孩子,将运算符结点入栈
  • 最后栈顶结点就是表达式树的根结点

【解题思路】

  • 题中求的是布尔表达式,问改变某个变量的值后,整个式子的值是否变化。
    由此可知,我们需要求出一个布尔型的数组isChange
    • isChange[i]为真,表示第i个变量变化后,表达式的值会变化。
    • isChange[i]为假时,表示第i个变量变化后,表达式的值不会变化。
  • 考察表达式a|b,当a为0,b为1时,0|1结果为0。此时如果a发生改变,式子变为1|1,结果为1。如果b发生改变,式子变为0|0结果为0。说明a的改变可以改变表达式的值,而b的改变不会改变表达式的值。
    由于运算数只有0、1,运算符只有&、|,可以枚举不同的表达式中改变两个变量后能否改变表达式的值,将这些情况保存起来,设为两个数组isChAnd和isChOr。
    • isChAnd[a][b][c]: 在a&b这一表达式中,a(c为0)或b(c为1)的值变化能否影响运算的结果。
    • isChOr[a][b][c]: 在a|b这一表达式中,a(c为0)或b(c为1)的值变化能否影响运算的结果。
  • 通过后缀表达式建立表达式树
    • 叶子结点记录变量编号(x),变量值(val)
    • 每个子树都是一个表达式。分支结点记录以该结点为根的子树所表示的表达式的值(val)、该结点的值如果变化对父结点的值是否有影响(inf)。
  • 判断哪些变量变化会对整体表达式的值有影响,构造isChange数组
    • 如果一个结点的变化对其父结点没有影响,那么该结点所有孩子结点的变化对父结点都没有影响。即以该结点为根结点的子树的叶子结点对应的变量的变化对整体表达式的值没有影响。
    • 遍历表达式树,如果遇到自身值变化不会影响父结点的结点,那么,就不再向下遍历。以此方法,遍历到的所有叶子结点对应的变量,其变化都会影响整体表达式的值,对应地设置isChange数组。
  • 根据题目需要,查询isChange数组,输出结果。

【题解代码】

解法1:枚举写出
#include <bits/stdc++.h>
using namespace std;
#define N 1000005
typedef struct Node//表达式树结点 
{
    int left, right;
    bool val;//值
    bool inf;//本节点值的改变是否能影响父结点
    int x;//该结点的变量编号
}Node;
bool val[N];//val[i]:变量x[i]的初值 
char s[N];//读入的字符串 
Node node[N];//结点池 
int ni = 1;//结点池下标
bool isChange[N];//isChange[i] 第i个变量变化后,能否影响最终结果
//isChAnd[a][b][c]: 当第1个数的值是a,第2个数值是b时,第c+1个数的值变化能否影响运算的结果。
//例:0&0 = 0 第一个数0变为1,公式变为1&0 = 0,不能影响运算结果。那么有isChAnd[0][0][0] = false;
bool isChAnd[2][2][2], isChOr[2][2][2];
//初始化isChAnd和isChOr
void initIsCh()
{
    for(int i = 0; i <= 1; ++i)
        for(int j = 0; j <= 1; ++j)
        {
            isChAnd[i][j][0] = (!i && j) != (i && j);
            isChAnd[i][j][1] = (i && !j) != (i && j);
            isChOr[i][j][0] = (!i || j) != (i || j);
            isChOr[i][j][1] = (i || !j) != (i || j);
        }
}
//生成表达式树,返回根节点的地址
int initTree()
{
    stack<int> stk;//存放结点地址
    int xNum = 0, np, lp, rp;//xNum:变量编号 np:新结点地址 lp,rp:左右孩子地址 
    char cal;//运算符
    int len = strlen(s);
    for(int i = 0; i <= len; ++i)
    {
        if(s[i] >= '0' && s[i] <= '9')
            xNum = xNum * 10 + s[i] - '0';
        else if(s[i] == '&' || s[i] == '|' || s[i] == '!')
            cal = s[i];
        else if(s[i] == ' ' || s[i] == '\0')
        {
            if(xNum > 0)//提取出x
            {
                np = ni++; 
                node[np].val = val[xNum];
                node[np].x = xNum;
                stk.push(np);
                xNum = 0;
            }
            else//提取出运算符
            {
                np = ni++;
                if(cal == '!')
                {
                    lp = stk.top();
                    stk.pop();
                    node[lp].inf = true;//取非运算的运算数的改变会影响表达式的值
                    node[np].val = !node[lp].val;
                    node[np].left = lp;
                }
                else
                {
                    rp = stk.top();
                    stk.pop();
                    lp = stk.top();
                    stk.pop();
                    node[np].left = lp;
                    node[np].right = rp;
                    if(cal == '&')
                    {
                        node[np].val = node[lp].val && node[rp].val;
                        node[lp].inf = isChAnd[node[lp].val][node[rp].val][0];
                        node[rp].inf = isChAnd[node[lp].val][node[rp].val][1];
                    }
                    else if(cal == '|')
                    {
                        node[np].val = node[lp].val || node[rp].val;
                        node[lp].inf = isChOr[node[lp].val][node[rp].val][0];
                        node[rp].inf = isChOr[node[lp].val][node[rp].val][1];
                    }
                }
                stk.push(np);
            }
        }
    }
    return stk.top();
}
//设置isChange数组 遍历表达式树
void initIsChange(int p)
{
    if(node[p].inf)//如果该点值的变化不能影响父结点 ,就不遍历了。
    {
        if(node[p].left == 0 && node[p].right == 0)
            isChange[node[p].x] = true;//如果遍历到叶子结点,那么设置该变量的变化可以影响表达式的值
        else
        {
            if(node[p].left != 0)
                initIsChange(node[p].left);
            if(node[p].right != 0)
                initIsChange(node[p].right);
        }
    }
}
int main()
{
    initIsCh();
    int n, q, qi;
    bool res;//正常计算的结果
    cin.getline(s, N);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)//输入各个变量的值
        scanf("%d", &val[i]);
    int root = initTree();//生成表达式树
    res = node[root].val;//通过已有变量值求出的表达式的原始结果
    node[root].inf = true;//为了配合下面的函数,必须设这一点
    initIsChange(root);//设置isChange数组
    scanf("%d", &q);//输入查询组数
    for(int i = 1; i <= q; ++i)
    {
        scanf("%d", &qi);//改变第qi变量
        if(isChange[qi])//如果第qi变量的改变能引起表达式值的变化
            printf("%d\n", !res);//输出变化后的结果
        else
            printf("%d\n", res);//输出原结果
    }
    return 0;
}
  • 写法2:
#include <bits/stdc++.h>
using namespace std;
#define N 1000005
struct Node
{
	int n, x;//x:该结点表示的变量的编号 
	char c;
	int left, right;
};
Node node[N];
int p, n, x[100005];
bool isChange[100005];//isChange[i]:xi变化后会影响整个表达式的值 
bool chAnd[2][2][2], chOr[2][2][2];//chAnd[i][j][k]:i & j 运算中第 k个数字发生变化能否影响结果
void init()
{
	for(int i = 0; i <= 1; ++i)//i:第1个运算数 
		for(int j = 0; j <= 1; ++j)//j:第2个运算数
		{
			chAnd[i][j][0] = (i && j) != (!i && j);
			chAnd[i][j][1] = (i && j) != (i && !j);
			chOr[i][j][0] = (i || j) != (!i || j);
			chOr[i][j][1] = (i || j) != (i || !j);
		}	
}

void dfs(int r) 
{
	if(node[r].left == 0 && node[r].right == 0)
	{
		isChange[node[r].x] = true;
		return;
	} 
	int ln = node[node[r].left].n, rn = node[node[r].right].n;
	if(node[r].c == '&')
	{
		if(chAnd[ln][rn][0])
			dfs(node[r].left);
		if(chAnd[ln][rn][1])
			dfs(node[r].right);
	}
	else if(node[r].c == '|')
	{
		if(chOr[ln][rn][0])
			dfs(node[r].left);
		if(chOr[ln][rn][1])
			dfs(node[r].right);
	}
	else //!
		dfs(node[r].right);
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	init();
	string s;
	int num = 0, q, j;
	getline(cin, s);
	cin >> n;
	for(int i = 1; i <= n; ++i)
		cin >> x[i];
	stack<int> stk;
	for(int i = 0; i < s.length(); ++i)
	{
		if(s[i] == 'x')
			continue;
		else if(s[i] >= '0' && s[i] <= '9')
			num = num*10+s[i]-'0';
		else if(s[i] == ' ')
		{
			if(num > 0)
			{
				int np = ++p;
				node[np].n = x[num];
				node[np].x = num;
				stk.push(np);
				num = 0;
			}
		}
		else
		{
			int np = ++p;
			node[np].c = s[i];
			node[np].right = stk.top();
			stk.pop();
			if(s[i] == '!')
				node[np].n = !node[node[np].right].n;
			else  
			{
				node[np].left = stk.top();
				stk.pop();
				if(s[i] == '&')
					node[np].n = node[node[np].left].n && node[node[np].right].n;
				else
					node[np].n = node[node[np].left].n || node[node[np].right].n;
			}
			stk.push(np);
		}
	}
	int root = stk.top(), val = node[root].n;
	dfs(root);
	cin >> q;
	while(q--)
	{
		cin >> j;
		if(isChange[j])
			cout << !val << '\n';
		else
			cout << val << '\n';
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值