HDU - 3350 - #define is unsafe
https://vjudge.net/problem/HD...
一句话题意:对一个含有
MAX()
宏与'+'
与','
的表达式进行计算,并求出要计算多少次加法;其中#define MAX(a,b) ((a)>(b)?(a):(b))
。
宏是基于“替换”工作的。
这也就是所谓的“正则序”(参考SICP第一章),不断地将宏名展开,反复进行替换过程,直到无可替换。
也就是说,MAX(1+2,3)
会被替换为(1+2)>(3)?(1+2):(3)
。而计算替换之后的表达式的值则需做2
次加法。比起传入函数max(int a,int b){return a>b?a:b;}
内,多计算了一次加法。
解:
我们可以对整个表达式字符串左往右扫一次,对遇到的是啥字符分类讨论。这也是我上一篇文章提到的“局部分离”的一种体现——先不看整体,先着眼局部——局部正确了,整体自然正确
。这也就是所谓的分而治之(Divide & Conquer)
。这种详细列出每种情形的思想,也差不多可以算作有限状态机
和所谓状态转移
的思想。
这个题目,用递归做。我是这么想的:
1.先实现一个最简单的加法/纯数字表达式解析函数,叫read
好了(其实应该叫parse
比较好)。
这个
read
函数要解析不含括号,只含+
的表达式。那么从左到右遍历一次吧!遇到
加号
:把临时寄存器
里的数字加进结果寄存器
里,并且加法计数器
加1;遇到
数字
就根据十进制数字转换法加进临时寄存器
里(方法是,临时寄存器
=临时寄存器
*10+遇到的这个数字
);边界条件:读完表达式时,
临时寄存器
里的数字再加进结果寄存器
里。(也可以在表达式字符串读进来之后,末尾加上一个+
,再让加法计数器
减1,这算不算够优雅呢?)。
仔细体会:这个最简单的加法表达式解析程序只有三个状态:1.遇到加号;2.遇到数字;3.边界,即字符串读完。
2.再扩展上一步的read
函数,使其支持含前、后括号
与含逗号
的表达式。
题意有如下约定:前括号总和
MAX
一起出现,如MAX(
。因此,可以忽略MAX这些字符,用前括号代表MAX
宏。仍然是从左向右扫一遍。索引(index)记为
i
;但不用for的自增i
(如for(;;i++)
),我们手动根据不同的状态增加。新增状态* 遇到
前括号
,根据题意,输入的表达式一定合法。所以目前遇到前括号
,则以后一定会遇到后括号
,那么现在直接递归提取出这对括号内的逗号两边的值,算一发吧!把下一个字符的i
传给函数,i
置为函数返回值,递归读出逗号前边的表达式,记为op1
(operand1)。然后,把下一个字符的i
传给函数,i
置为函数返回值
,递归读出逗号前边的表达式,记为op2
(operand2);根据MAX的法则,计算MAX(op1,op2)
的结果。return read(i+1)
。做完这个步骤之后,这对括号内的表达式就解析完毕了。新增状态* 遇到
逗号
,结果寄存器+=临时寄存器
,临时寄存器 = 0
,return i
;新增状态* 遇到
后括号
,同上一条。新增状态* 遇到
字母
,i++
,即无视这个字符,去看下一个字符咯。修改状态* 遇到
加号
, 在第一步的基础上,return read(i+1)
。文字描述难免难懂。请看代码。
3.收工
真的,实现了上面两个功能,这题就做完了。实现的细节用到了
c++的引用机制
。就是你看到的&
。递归 + 引用,描述了解析实现的本质,美滋滋。注释1:
bl
这个结构体是啥的简写呢?……我突然忘了,应该是blanket
的简写。完全意义不明…注释2:
bl
内的value
代表表达式的值,而cnt
是count
的简写,代表这个表达式内部进行了多少次加法
。
//AC,0ms
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<sstream>
#include<algorithm>
#include<iostream>
#include<stack>
#include<vector>
#include<set>
#include<map>
#define LL long long
using namespace std;
struct bl{
int value,cnt;
bl(int v=0,int c=0):value(v),cnt(c){};
};
string s;
int read(int index,bl &p){
int i,num=0;
for(i = index;i<s.length();){
if(s[i] == '('){
bl op1,op2;
i = read(i+1,op1);
i = read(i+1,op2);
if(op1.value > op2.value){
p.cnt += 2*op1.cnt + op2.cnt;
p.value += op1.value;
}else{
p.cnt += 2*op2.cnt + op1.cnt;
p.value += op2.value;
}
return read(i+1,p);
}else if(s[i] == ')'){
p.value += num;
num = 0;
return i;
}else if(s[i] == ','){
p.value += num;
num = 0;
return i;
}else if(s[i] == '+'){
p.value += num;
p.cnt ++;
num = 0;
return read(i+1,p);
}else if(s[i] >='0' && s[i] <='9'){
num = num*10 + s[i] - '0';
i++;
}else{
i++;
}
}
if(num) p.value+=num;
return i;
}
int main(){
int t;
cin >> t;
while(t--){
cin >> s;
bl re;
read(0,re);
cout << re.value << " " << re.cnt << endl;
}
return 0;
}
嗨呀,递归真是漂亮啊!
recursion a day,keep the stack away!
括号表达式解析就是要递归才对吧!!!
……不过话说回来,我想出这个递归解法用了好久时间。我也是半路用上状态思想的。
但是只要想清楚每种状态
及其转移
,实现就如探囊取物了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。