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)。做完这个步骤之后,这对括号内的表达式就解析完毕了。

  • 新增状态* 遇到逗号结果寄存器+=临时寄存器临时寄存器 = 0return i

  • 新增状态* 遇到后括号,同上一条。

  • 新增状态* 遇到字母i++,即无视这个字符,去看下一个字符咯。

  • 修改状态* 遇到加号, 在第一步的基础上,return read(i+1)

  • 文字描述难免难懂。请看代码。

3.收工

  • 真的,实现了上面两个功能,这题就做完了。实现的细节用到了c++的引用机制。就是你看到的&。递归 + 引用,描述了解析实现的本质,美滋滋。

  • 注释1:bl这个结构体是啥的简写呢?……我突然忘了,应该是blanket的简写。完全意义不明…

  • 注释2:bl内的value代表表达式的值,而cntcount的简写,代表这个表达式内部进行了多少次加法

//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!

括号表达式解析就是要递归才对吧!!!

……不过话说回来,我想出这个递归解法用了好久时间。我也是半路用上状态思想的。

但是只要想清楚每种状态及其转移,实现就如探囊取物了。


suicca
82 声望2 粉丝

为在座的各位献上一曲WHITE ALBUM (WRONG ANSWER)


引用和评论

0 条评论