清空全网题目系列 · 洛谷 · P1054 [NOIP 2005 提高组] 等价表达式
说明
说明
本系列由 Lee_King_Jimmy 推出,目的是将全网的题目都解一遍
这是第 444 篇
上一篇: 清空全网题目系列 · SPOJ · Sum the Square
下一篇: 未完待续
题目位置
https://www.luogu.com.cn/problem/P1054
思路
首先我们讲解后缀表达式
后缀表达式
我们平时使用的表达式是中缀表达式,虽然我们自己看着很顺眼 试问在复杂的题目中的公式很顺眼吗?,但是计算机却不行 深有同感
后缀表达式就是一个没有括号,没有优先级的表达式,符号全部都在数字的后面。
计算后缀表达式
我们要使用一个栈,执行以下操作:
- 遇到的是一个数字,我们将这个数字压到这个栈里面
- 遇到的是一个符号,我们取出栈顶的两个数,将这两个数字运算后重新压入栈内
中缀表达式转后缀表达式
-
如果为数,则直接添加到后缀表达式中。
-
如果为运算符 +,-,*,^,则重复将“运算符栈”栈顶元素添加到后缀表达式中,再将“运算符栈”栈顶元素从“运算符栈”中弹出,直到“运算符栈”栈顶元素优先级小于该运算符优先级或“运算符栈”为空,再加入该运算符。
-
如果为左括号 (,则直接加入“运算符栈”。
-
如果为右括号 ),则重复弹出“运算符栈”栈顶元素,直到左括号 ( 也弹出。
当表达式内所有元素处理完毕后,依次弹出“运算符栈”所有元素并添加到后缀表达式中。
接着
Q1: 怎么解决 a 这个未知数
我们可以计算出每一个后缀表达式的答案,a 可以取一个质数,比如 10007
Q2: 数据太大爆 long long
我们可以选择一个大质数作为模数
每一次计算取模即可,注意减法要减去之后加上这个模数再次模一遍
Q3: 为什么会 RE
因为输入数据的时候我们要使用 getchar
输入数据,如果使用 gets
getline
等读取一行的函数,字符串的末尾会多出一个 \r
, 详细见代码
实现代码
#include <stdio.h>
#include <ctype.h>// 一个数字栈
long long num_st[1024], top_num = 0;
// 一个符号栈
char sign_st[1024]; int top_sign = 0;typedef long long ll;
const int MOD = 1e9 + 7, A = 100007; // MOD 要是一个质数, A=91?一个随机的数字ll power(ll x, ll y) {if(!y) return 1;ll tmp = power(x, y >> 1);(tmp *= tmp) %= MOD;if(y & 1) (tmp *= x) %= MOD;return tmp;
}
void OpCalc(char op) { // 计算数字栈的栈顶两个数使用 op 符号的答案ll b = num_st[top_num--]; // 栈顶元素switch (op) {case '+': num_st[top_num] = (num_st[top_num] + b) % MOD; break;case '-': num_st[top_num] = (num_st[top_num] - b + MOD) % MOD; break; // 注意负数case '*': num_st[top_num] = (num_st[top_num] * b) % MOD; break;case '^': num_st[top_num] = power(num_st[top_num], b); break;}
}
int get(char x) {if (x == '+' || x == '-') return 1;if (x == '*') return 2;if (x == '^') return 3;return 0;
}// 读入一个表达式并且计算
char str[1024] = {0};
int cur = 0;ll p()
{char ch; top_num = top_sign = 0; // 将两个栈置为空while ((ch = getchar()) == '\n' || ch == '\r'); // 将换行符吃了do {int tmp_val = 0, ck = false;while (isdigit(ch)) tmp_val = (tmp_val * 10 + (ch - '0')) % MOD, ch = getchar(), ck = true; // 这里要 mod, 这里是读入表达式中的数字if (ck) num_st[++top_num] = tmp_val; // 保存答案if (ch == '\n' || ch == '\r') break; // 该退出了if (ch == 'a') num_st[++top_num] = A; // A 的值// 处理符号if (ch == '(') sign_st[++top_sign] = '(';else if (ch == ')') { // 处理右括号while (top_sign && sign_st[top_sign] != '(') OpCalc(sign_st[top_sign--]); // 计算元素top_sign--; // 注意这里还是要--, 删除最后的左括号}else if (ch == '+' || ch == '-' || ch == '*' || ch == '^')// +, -, *, ^{while (top_sign && get(ch) <= get(sign_st[top_sign])) OpCalc(sign_st[top_sign--]); // 计算元素sign_st[++top_sign] = ch; // 保存当前这个符号}} while ((ch = getchar()) != '\n' && ch != '\r'); // 不是换行符while (top_sign) OpCalc(sign_st[top_sign--]); return num_st[1];
}int main()
{// freopen("equal.in", "r", stdin);// freopen("equal.out", "w", stdout);ll sample = p();printf("%s", str);int n;scanf("%d", &n);for (int i = 0; i < n; i++){if (p() == sample) putchar('A' + i);}return 0;
}
时间复杂度分析
时间复杂度为 O(nT)O(nT)O(nT) TTT 表示字符串的长度
最大数据为 O(26×50)=O(1300)O(26 \times 50) = O(1300)O(26×50)=O(1300) 水过,跑出 1ms1ms1ms 的高速