Time Limit: 1 second
Memory Limit: 50 MB
【问题描述】
明明进了中学之后,学到了代数表达式。有一天,他碰到一个很麻烦的选择题。这个题目的题干中首先给出了一个代数表达式,然后列出了若干选项,每个选项也是一个代数表达式,题目的要求是判断选项中哪些代数表达式是和题干中的表达式等价的。这个题目手算很麻烦,因为明明对计算机编程很感兴趣,所以他想是不是可以用计算机来解决这个问题。假设你是明明,能完成这个任务吗?这个选择题中的每个表达式都满足下面的性质: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(2 <= n <= 26),表示选项的个数。
接下来的N行,每行包括一个选项中的表达式。这n个选项的标号分别是A,B,C,D…… 输入中的表达式的长度都不超过50个字符,而且保证选项中总有表达式和题干中的表达式是等价的。
【输出】
包含1行,这一行包括一系列选项的标号,表示哪些选项是和题干中的表达式等价的。选项的标号按照字母顺序排列,而且之间没有空格。
【输入样例】
( 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%的数据满足:表达式中只可能出现两种运算符‘+’和‘-’对于其他的数据满足:对于其它的数据,四种运算符‘+’,‘-’,‘*’,‘^’在表达式中都可能出现
100%的数据满足:表达式中都可能出现小括号‘(’和‘)’
【题解】
这是一道表达式求值的问题。
判断是否等价。需要用一系列的数字来判断。即把a代成相同的数字然后求两个表达式的值,如果相同则判断为相同。为了防止偶然性误差。得多弄几个a值。要在代所有的数字答案都相同。才能判断这个表达式和目标表达式是等价的。
用二分的方法求表达式的值。
具体的。先找到表达式中。计算优先级别最小的操作符。然后以这个操作符为划分依据,分为左半边和右半边。进行递归的过程。如果当前处理的字符只有数字。则直接返回这个数字。如果只有a则返回我们当前给a定义的值(程序中有进行3次验算);
然后不能直接把答案计算出来。因为可能会溢出。所以要取模。这个用来取模的数字,不能随便取。不然会错解。
然后是一开始输入的表达式。
它可能会出现右括号多余的情况。需要去除。
同时也可能出现左括号多余的情况。同样要去除。
去除的方法要看我的程序里的操作。不能简单的看到一个右括号就去掉。
//还有多余空格也要去掉。
【代码】
#include <cstdio> #include <stdlib.h> #include <iostream> #include <string> using namespace std; const int xx[4] = {0,-2,-3,551}; const int mo = 2551; string s; int daiti,ans[4],n; void check(string & s) //因为是要直接对s处理,所以要加个&表示对s操作。不加的话没有效果。 { int l = s.size()-1; for (int i = 1;i <= l;i++) //如果看到空格就置为一个标记符 if (s[i] == ' ') s[i] = '$'; string sss =" "; for (int i = 1;i <= l;i++) //把不是标记符的取出来表示去除过程 if (s[i]!='$') sss+=s[i]; s = sss; l = s.size()-1; int b = 0; for (int i = 1;i <= l;i++) //这里检验是否有多括号的情况 if (s[i] == '(') b++; else if (s[i] == ')') b--; if (b < 0) //如果右括号多了 { for (int i = l;i >= 2;i--) //从尾至首去除连续的右括号(数据是这样的多右括号情况) if (b < 0 && s[i] == s[i-1] && s[i] == ')') //置为标记符 s[i-1] = '$',b++; else if (b == 0) //如果括号不多了就结束 break; } if ( b > 0) //如果左括号多了 for (int i = 1;i <= l;i++) if (b >0 && s[i] == '(') //遇到一个左括号就直接置为标记符 s[i] = '$',b--; else if (b == 0) break; l = s.size()-1; string ss = " "; for (int i = 1;i <= l;i++) //把不是标记符的记录下来,也即去除多余括号的过程 if (s[i] != '$') ss += s[i]; s = ss; } bool reducebracket(string & s) { int l= s.size() - 1; if (s[1] != '(' || s[l] != ')') //如果两边不是括号则返回不用去除 return false; int b = 0; //这是防止(1+3) +(2+4)这样的情况。虽然两边都是配对的括号,但是不用去除 for (int i = 2;i <= l-1;i++) //所以我们从第二个字符到l-1判断这里面的括号是否配对 { if (s[i] == '(') b++; if (s[i] == ')') b--; if (b < 0) //如果不配对。则一定是上述情况。则返回不用去除括号 return false; } s = s.erase(l,1); s = s.erase(1,1); //否则去除括号并返回true表示要继续尝试去除括号 return true; } char cmp(char x,char y) //判断x操作符和y操作符哪一个的计算优先级别更低 { if ( x == '$') //如果是初始值,则一定可以更新 return '>'; if ( x == '^') //乘幂是最高优先级别 所以也可以判断更小(如果y也是乘幂也应该这样。) //这样我们获取的乘幂就是靠后的。而我们所找到的靠后的。正符合递归。是后算的。 return '>'; if ( x == '*' && y !='^') //如果是乘,且y不是乘幂,则也可以判断。 //如果y也是cheng则同理 return '>'; if ( (x == '+' || x == '-') && ( y == '+' || y == '-')) //如果都是加减法。则也找靠后的 return '>'; return '<';//其他情况则是小于 } int find(string s) { int l = s.size()-1; char now = '$'; //这个字符用来作为初始值 int i = 1,k; while (i <= l) { if (s[i] == '(') //如果是括号则要跳过 如(1+2)+(3*4),则我们要跳过括号内容找到+号的位置 { int b = 0;//用栈的思路来跳过这些括号 do { if (s[i] == '(') b++; if (s[i] == ')') b--; i++; } while (b != 0); } else //如果是操作符,就判断一下是否比当前找到的操作符级别更低,如果是就更新 if ( (s[i] == '*' || s[i] == '+' || s[i] == '^') || (s[i] == '-' && i > 1 && (s[i-1] == ')' || (s[i-1] >='0' && s[i-1] <= '9') || (s[i-1] == 'a') )) ) { if (cmp(now,s[i]) == '>') //用自己写的cmp函数来判断 { now = s[i]; k = i; } i++; //不管这个字符是什么都要递增循环变量 } else i++; } return k; } int x_y(int x, int y) //获取x^y mod mo 的值 { int tt = 1; //y == 0时 为1 for (int i = 1;i <= y;i++) //边乘边取模即可。 tt = (tt * (x % mo)) % mo; return tt; } int reduce(string s) { if (s == " ")//如果是一个空字符(在递归的时候有给递归字符前加一个空格) return 0; if (s == " a") //如果是a就返回代替值 return daiti; bool judge = false; //judge用于判断这个字符里面是否有运算符 int l = s.size()-1; for (int i = 1;i <= l;i++) if (s[i] == '*' || s[i] == '+' || s[i] == '^') //加和乘和乘幂可以直接判断 { judge = true; break; } else //减号的话要判断是不是因为这个数字是负数。 if (s[i] == '-' && i > 1 && (s[i-1] == ')' || (s[i-1] >='0' && s[i-1] <= '9') || (s[i-1] == 'a') )) //如果这个负号前面有右括号,或者a或者数字。则一定是运算符 { judge = true; break; } if (not judge) //如果没有运算符则就是一个数字 直接转换成数字然后返回就可以了 { int x = atoi(s.c_str()); return x; } bool flag = reducebracket(s); //这是去除两边的括号 如 (1+3)则这步会去除两边括号变成1+3 while (flag) //可能有多层括号所以要多次去除 { flag = reducebracket(s); } int k = find(s); //k表示s这个字符串里面运算优先级别最小的操作符所在的位置 char key = s[k]; //获取这个操作符是什么 string s1 = s.substr(0,k); //然后把操作符的左边和右边截取出来 s = s.erase(0,k+1); s = " " + s; //要在右边前面加上空格。便于操作 string s2 = s; int x = reduce(s1),y = reduce(s2);//递归获取左边和右边的值 switch (key) //判断这个操作符是什么,然后做相应的运算。 { case ('+'): return ( ( (x % mo)+ (y % mo)) % mo); //注意要边取模 不然会有溢出风险 break; case ('-'): return ( ( (x %mo)-(y%mo)) % mo); break; case ('*'): return ( ((x % mo)*(y % mo)) % mo); break; case ('^'): return ( x_y(x,y)); break; } } void input_data() { string ss; getline(cin,ss); s = " "; //给字符串前面加一个空格。这样我们要处理的字符就在1到长度这个区间了 s+=ss; check(s);//去掉多余的括号 和空格。(从1开始去除。首部那个空格不要去掉。) for (int i = 1;i <= 3;i++) { daiti = xx[i]; //代替a的数字 ans[i] = reduce(s); //表示所给的初始表达式中a用xx[i]代替算出来的结果 } scanf("%d",&n); getchar();//在用getline之前一定要加一句getchar for (int i = 1;i <= n;i++) //读入n个表达式 { string ts; getline(cin,ts); string xuanxiang = " "; //还是一样要在前面加一个空格 xuanxiang+=ts; check(xuanxiang); for (int j = 1;j <= 3;j++) //然后用3个代替值都代入表达式。进行计算 { daiti = xx[j]; int dd = reduce(xuanxiang); if (dd != ans[j]) //如果结果和所给的表达式的值不同。则跳过 break; char o_ut = 'A'+i-1; if (j == 3) //如果3个代替值代入表达式,最后结果都和所给表达式的结果相同则输出选项 cout << o_ut; } } } int main() { //freopen("F:\rush.txt","r",stdin); input_data(); return 0; }