一道表达式模拟题
题目
现有一个被压缩的字符串(有小写字母、括号、数字组成),压缩规则如下所示。
压缩后:
2(2u2lt4d)3(rb)pa
压缩前:
uulltdddduulltddddrbrbrbpa
现给定压缩后的字符串,求26个小写字母在源字符串中分别出现的次数。你可以假设输入是合法的。
数据范围:
设N为压缩后的字符串长度,M为压缩前的字符串长度。
1 <= N <= 1000
1 <= M <= 2^60
思考
本质上是模拟一个计算器。
2(2u2lt4d)3(rb)pa
相当于(2*(2*u+2*l+t+4*d)+3*(r+b)+p+a)
. 这里整个表达式外面再套一对括号是避免最后再evaluate的情况(比如到最后剩下2*a
就还得calc一次,但(2*a)
在碰到最后一个)
的时候就会把2*a
算掉)。
用map<char, int>储存字母出现的次数。
这题比224难的点在于: 1. 有乘号,所以要考虑优先级;2. 操作数有数字和map两种类型。
难点解析
优先级
只要在碰到“+”号的时候把前面的“*”号都evaluate掉就可以了。
操作数类型
乘法运算定义为:number * map (map中所有频率 *= number)
加法运算定义为:map + map (对应字母的频率加起来)
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <map>
#include <unordered_set>
#include <set>
#include <vector>
#include <queue>
#include <numeric>
#include <stack>
using namespace std;
using LL = long long;
string in;
bool isNum(char c) {
return '0' <= c && c <= '9';
}
bool isAlpha(char c) {
return 'a' <= c && c <= 'z';
}
void calc(stack<LL>& nums, stack<unordered_map<char, LL>>& maps, stack<char>& ops) {
if (ops.top() == '*') {
auto m = maps.top(); maps.pop();
auto num = nums.top(); nums.pop();
for (auto& [_, v] : m) v *= num;
maps.push(m);
} else {
auto m1 = maps.top(); maps.pop();
auto m2 = maps.top(); maps.pop();
for (auto [k, v] : m2) m1[k] += v;
maps.push(m1);
}
ops.pop();
}
void solve() {
int n = in.size();
string s = "(";
for (int i = 0; i < n; i++) {
char c = in[i];
if (isAlpha(c)) {
if (i && isNum(in[i - 1])) {
s += '*';
}
if (i && (isAlpha(in[i - 1]) || in[i - 1] == ')')) {
s += '+';
}
s += c;
}
else if (isNum(c)) {
if (i && in[i - 1] != '(' && !isNum(in[i - 1])) s += '+';
s += c;
}
else if (c == '(') {
s += '*'; s += c;
}
else s += c;
}
s += ")";
// cout << s << endl;
string buffer;
stack<LL> nums;
stack<unordered_map<char, LL>> maps;
stack<char> ops;
for (char c : s) {
if (isNum(c) || isAlpha(c)) {
buffer += c;
} else if (!buffer.empty()) {
if (isNum(buffer[0]))
nums.push(stoll(buffer));
else {
unordered_map<char, LL> tmp;
for (char cc : buffer) tmp[cc] += 1;
maps.push(tmp);
}
buffer = "";
}
if (c == '(') {
ops.push('(');
} else if (c == '+') {
while (ops.top() != '(') {
calc(nums, maps, ops);
}
ops.push('+');
}
else if (c == '*') ops.push('*');
else if (c == ')'){
while (ops.top() != '(') {
calc(nums, maps, ops);
}
ops.pop();
}
}
//cout << maps.empty();
auto m = maps.top();
for (char c = 'a'; c <= 'z'; c++) {
cout << c << ' ' << m[c] << endl;
}
}
int main(){
// 自分の得意な言語で
// Let's チャレンジ!!
cin >> in;
solve();
return 0;
}
优化
map可以换成长度为26的vector。
总结
计算器类题目的要点:
- 双栈储存操作数和操作符。
- 用buffer来储存未读取完的操作数。
- 用calc函数抽象出运算操作。
- 在整个表达式外面套一对括号可以起到“哨兵”的作用,减少特殊判断。