表达式求解以及一个NOIP老题——后缀表达式+栈

表达式求解

问题描述

  输入一个表达式,表达式包括括号,整数及运算符(加+、减-、乘*、除/),运算的中间数据及结果不超出实型,表达式不包括其它多余字符,编程计算结果。表达式不需要判错。

输入格式

  一个表达式,不超出250字符。

输出格式

  表达式结果。保留两位小数。

样例输入

9*(6/2)+6*3*(25-5*2)-3

样例输出

294.00

Analysis

中序表达式比较麻烦,不如我们转化成后缀表达式再慢慢计算。

中缀转后缀是个复杂的过程…百度百科中给的标准过程是这样的。

    步骤如下:

    初始化两个栈:运算符栈s1和储存中间结果的栈s2;

    从左至右扫描中缀表达式;

    遇到操作数时,将其压s2;

    遇到运算符时,比较其与s1栈顶运算符的优先级:

        如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

        否则,若优先级比栈顶运算符的高,也将运算符压入s1(注意转换为前缀表达式时是优先级较高或相同,而这里则不包括相同的情况);

        否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

    遇到括号时:

        如果是左括号“(”,则直接压入s1;

        如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;

    重复步骤2至5,直到表达式的最右边;

    将s1中剩余的运算符依次弹出并压入s2;

依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式(转换为前缀表达式时不用逆序)

没错,照着这个打即可。

转化为后缀表达式之后就是计算了,后缀表达式的计算很简单。百度百科给的计算方法是这样的:

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。

还是照着打,然后就出来了。

Code

#include<bits/stdc++.h>

using namespace std;

typedef double db;

struct node{
	db a;
	char c;
	bool flag;//标记究竟是db 还是char
};
node ne;
int hash[130];//用于记录运算符的优先级
stack<node>A,B;
stack<db>D;//用于计算
node C[333];
int js=0;

void build(){
	hash['-']=hash['+']=1;
	hash['*']=hash['/']=2;
}

void print(){
	node lin;
	if (!B.empty()){
		lin=B.top();
		B.pop();
		print();
		++js;
		C[js]=lin;
	}
}

int main(){
//	freopen("test.in","r",stdin);
//	freopen("test.out","w",stdout);
	string s;
	build();//建立运算符优先级之间的关系
	cin>>s;
	int st=s.length();
	int p=0,temp;
	char top;//记录栈顶操作符
	node lin;//临时转存的node,下面计算时还要用到
	for (int i=0;i<st;++i){
		if (s[i]<='9' and s[i]>='0'){//当扫描到数字
			temp=s[i]-'0';
			p=i+1;
			while (p<st and s[p]<='9' and s[p]>='0'){//短路表达式,防止错误
				temp=temp*10+s[p]-'0';
				++p;
			}
			ne.flag=true;//标记为数字
			ne.a=(db)temp;//储存这个数字
			B.push(ne);//压入第二个栈中
			if (p==st)//最后一位,直接跳出
				break;
			i=p-1;//s[p]处是个运算符,下次应该转到i=p这个位置,这里-1,进入循环自动+1
			continue;//直接进入下一次循环
		}
		//扫描到的不是数字
		ne.flag=false;//标记,将要进栈的是个操作符
		ne.c=s[i];
		if (!A.empty())//A中一定是字符
			top=A.top().c;
		else{//A为空,直接进栈,跳出
			A.push(ne);
			continue;
		}
		//处理是括号的情况
		if (s[i]=='('){
			A.push(ne);
			continue;
		}
		if (s[i]==')'){
			while (!A.empty() and A.top().c!='('){//弹出,直到左括号
				 lin=A.top();
				 A.pop();//弹
				 B.push(lin);
			}
			A.pop();//最后剩左括号,销毁之
			continue;//进入下一次循环
		}
		//然后下面就是四则运算的运算符
p1:;
		if (!A.empty())
			top=A.top().c;
		if (A.empty() or top=='('){//A为空,直接进栈
			A.push(ne);
			continue;
		}
		if (hash[s[i]]>hash[top]){//现在的优先级比栈顶运算符高
			A.push(ne);//直接进栈
			continue;
		}
		lin=A.top();
		A.pop();
		B.push(lin);
		goto p1;//转到p1反复循环,因为符合情况的都以continue结束而不会到这里
	}
	while (!A.empty()){//将A中所有元素转入B
		lin=A.top();
		A.pop();
		B.push(lin);
	}
	print();//将逆序转为正序
	db ans=0,ta,tb;
	for (int i=1;i<=js;++i){
		lin=C[i];
		if (lin.flag)//数字,压入栈
			D.push(lin.a);
		else{//操作符
			ta=D.top();
			D.pop();
			tb=D.top();//次顶元素 op 栈顶元素
			D.pop();
			if (lin.c=='+'){
				ans=tb+ta;
				D.push(ans);
			}
			if (lin.c=='-'){
				ans=tb-ta;
				D.push(ans);
			}
			if (lin.c=='*'){
				ans=tb*ta;
				D.push(ans);
			}
			if (lin.c=='/'){
				ans=tb/ta;
				D.push(ans);
			}
		}
	}
	ans=D.top();
	printf("%.2f\n",ans);
	return 0;
}

基础题打完了,我们来看一个久远的NOIP原题。

〖NOIP2005T〗等价表达式

问题描述

  明明进了中学之后,学到了代数表达式。有一天,他碰到一个很麻烦的选择题。这个题目的题干中首先给出了一个代数表达式,然后列出了若干选项,每个选项也是一个代数表达式,题目的要求是判断选项中哪些代数表达式是和题干中的表达式等价的。

  这个题目手算很麻烦,因为明明对计算机编程很感兴趣,所以他想是不是可以用计算机来解决这个问题。假设你是明明,能完成这个任务吗?

  这个选择题中的每个表达式都满足下面的性质:

  1. 表达式只可能包含一个变量‘a’。

  2. 表达式中出现的数都是正整数,而且都小于10000。

  3.  表达式中可以包括四种运算‘+’(加),‘-’(减),‘*’(乘),‘^’(乘幂),以及小括号‘(’,‘)’。小括号的优先级最高,其次是‘^’,然后是‘*’,最后是‘+’和‘-’。‘+’和‘-’的优先级是相同的。相同优先级的运算从左到右进行。(注意:运算符‘+’,‘-’,‘*’,‘^’以及小括号‘(’,‘)’都是英文字符)

  4. 幂指数只可能是1到10之间的正整数(包括1和10)。

  5. 表达式内部,头部或者尾部都可能有一些多余的空格。

  下面是一些合理的表达式的例子:

  ((a^1) ^ 2)^3,a*a+a-a,((a+a)),9999+(a-a)*a,1 + (a -1)^3,1^10^9……

输入格式

  第一行给出的是题干中的表达式。第二行是一个整数n(2 <= n <= 26),表示选项的个数。后面n行,每行包括一个选项中的表达式。这n个选项的标号分别是A,B,C,D……

  输入中的表达式的长度都不超过50个字符,而且保证选项中总有表达式和题干中的表达式是等价的。

输出格式

  包括一行,这一行包括一系列选项的标号,表示哪些选项是和题干中的表达式等价的。选项的标号按照字母顺序排列,而且之间没有空格。

样例输入

( a + 1) ^2
3
(a-1)^2+4*a
a + 1+ a
a^2 + 2 * a * 1 + 1^2 + 10 -10 +a -a

样例输出

AC

数据规模和约定

  对于30%的数据,表达式中只可能出现两种运算符‘+’和‘-’;

  对于其它的数据,四种运算符‘+’,‘-’,‘*’,‘^’在表达式中都可能出现。

  对于全部的数据,表达式中都可能出现小括号‘(’和‘)’。

Analysis

首先,看完题目,立马放弃模拟表达式化简这种方案,这太困难,而且如果出来^10^10这种操作怎么办。

那么很显然,把a替换掉,变成一个具体数字。

应该随机一个素数出来,不容易卡掉,但是我拼RP,没随机,直接代入9.

问题仍然是,如果出现^10^10这样的,怎么办。

很显然,幂取模。

于是基本方案就出来了,将表达式中的未知数替换成常数,然后计算,为了保证在int内,需要反复取模。

不过由于乘方号后面的数字小,可以直接循环乘,而不必幂取模。但是我还是打了。

那么怎么计算就成了问题。

这和上一题,表达式求值,一个样,转化成后缀表达式,然后计算。

拿上一题程序改一改就出来了吧。

但是,我一开始写的程序交上去全部RE。

问题是行末会有’\r’,’\n’这种字符。

然后我在读入的时候加了这句

if (s[i]==' ' or s[i]=='\n' or s[i]=='\r')

        continue;
交上去一看,10分,9个RE,又发生了什么?

呵呵!我只能猜是由于括号匹配问题了。

经过测试,我的程序无法应对括号不匹配这种情况。

难道数据里还有这种奇葩情况?

我在遇到“)”时,在之前是把A栈中内容弹出直到有“(”,由于使用了while语句,那么在while之后还要弹出一次,表示弹出”(“。

为了应对这种情况,要在while之后进行判断,如果栈内为空,就不弹了,不为空就弹。

还有就是把A中内容全部转入B这步,遇到括号或者回车或者空格要跳过。

然后一交,呵呵,80,又是什么问题?

去下了数据,发现,是在取栈top的时候溢出,因为栈已经为空了。

明明不应该有这种问题,哪里来的这种情况???

在取top的时候判断栈是否为空,如果是空的,直接入栈。

最后贴出AC代码。

Code

#include<bits/stdc++.h>

using namespace std;

const int mod = 12343;
const int Ch = 9;

struct node{
	int a;
	char c;
	bool flag;//标记究竟是db 还是char
	node(){
		a=1;
		c='1';
		flag=true;
	}
};
int hash[130];//用于记录运算符的优先级
stack<node>A,B;
stack<int>D;//用于计算
node C[333];
int js=0;

void build(){
	hash['-']=hash['+']=1;
	hash['*']=hash['/']=2;
	hash['^']=3;//最高级别的运算符
}

void print(){
	node lin;
	if (!B.empty()){
		lin=B.top();
		B.pop();
		print();
		++js;
		C[js]=lin;
	}
}

int modq(int a,int b){
	int res=1;
	while(b){
		a%=mod;
		if (b&1)
			res=res*a%mod;
		b/=2;
		a=a*a%mod;
	}
	return res;
}

int cal(string s){
	build();//建立运算符优先级之间的关系
	int st=s.length();
	int p=0,temp;
	char top;//记录栈顶操作符
	bool flag=false;
	node lin,ne;//临时转存的node,下面计算时还要用到
	for (int i=0;i<st;++i){
		if (s[i]==' ' or s[i]=='\n' or s[i]=='\r')
			continue;
		if (s[i]<='9' and s[i]>='0'){//当扫描到数字
			temp=s[i]-'0';
			p=i+1;
			while (p<st and s[p]<='9' and s[p]>='0'){//短路表达式,防止错误
				temp=temp*10+s[p]-'0';
				++p;
			}
			ne.flag=true;//标记为数字
			ne.a=temp;//储存这个数字
			B.push(ne);//压入第二个栈中
			if (p==st)//最后一位,直接跳出
				break;
			i=p-1;//s[p]处是个运算符,下次应该转到i=p这个位置,这里-1,进入循环自动+1
			continue;//直接进入下一次循环
		}
		if (s[i]=='a'){//扫描到a,直接替换成数字
			ne.flag=true;
			ne.a=Ch;//更改为常数
			B.push(ne);
			continue;
		}	
		//扫描到的不是数字
		ne.flag=false;//标记,将要进栈的是个操作符
		ne.c=s[i];
		if (!A.empty())//A中一定是字符
			top=A.top().c;
		else{//A为空,直接进栈,跳出
			A.push(ne);
			continue;
		}
		//处理是括号的情况
		if (s[i]=='('){
			A.push(ne);
			continue;
		}
		if (s[i]==')'){
			while (!A.empty() and !A.top().flag and A.top().c!='('){//弹出,直到左括号
				 lin=A.top();
				 A.pop();//弹
				 B.push(lin);
			}
			if (!A.empty())
				A.pop();//最后剩左括号,销毁之
			continue;//进入下一次循环
		}
		//然后下面就是四则运算的运算符
p1:;
		if (!A.empty())
			top=A.top().c;
		if (A.empty() or top=='('){//A为空,直接进栈
			A.push(ne);
			continue;
		}
		if (hash[s[i]]>hash[top]){//现在的优先级比栈顶运算符高
			A.push(ne);//直接进栈
			continue;
		}
		lin=A.top();
		A.pop();
		B.push(lin);
		goto p1;//转到p1反复循环,因为符合情况的都以continue结束而不会到这里
	}
	while (!A.empty()){//将A中所有元素转入B
		lin=A.top();
		A.pop();
		if (lin.c=='(' or lin.c==')' or lin.c==' ' or lin.c=='\n' or lin.c=='\r')
			continue;
		B.push(lin);
	}
	print();//将逆序转为正序
	int ans=0,ta,tb;
	for (int i=1;i<=js;++i){
		lin=C[i];
		if (lin.flag)//数字,压入栈
			D.push(lin.a);
		else{//操作符
			ta=D.top();
			D.pop();
			tb=D.top();//次顶元素 op 栈顶元素
			D.pop();
			if (lin.c=='+'){
				ans=(tb+ta)%mod;
				D.push(ans);
			}
			if (lin.c=='-'){
				ans=(tb-ta)%mod;
				D.push(ans);
			}
			if (lin.c=='*'){
				ans=(tb*ta)%mod;
				D.push(ans);
			}
			if (lin.c=='^'){
				ans=modq(tb,ta);
				D.push(ans);
			}
		}
	}
	ans=D.top();
	return ans;
}

int main(){
//	freopen("equal.in","r",stdin);
//	freopen("equal.ans","w",stdout);
	string s;
	getline(cin,s);
	int ans=0,temp,n;
	ans=cal(s);
	cin>>n;
	getline(cin,s);
	for (int i=1;i<=n;++i){
		getline(cin,s);
		js=0;
		while (!A.empty())
			A.pop();
		while (!B.empty())
			B.pop();
		memset(C,0,sizeof(C));
		while (!D.empty())
			D.pop();
		temp=cal(s);
		if (temp==ans)
			cout<<(char)(i+'A'-1);
	}
	cout<<endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值