HDU - 3350 - #define is unsafe - [基於遞歸的簡單表達式解析]

HDU - 3350 - #define is unsafe

https://vjudge.net/problem/HD...ios

一句話題意:對一個含有MAX()宏與'+'','的表達式進行計算,並求出要計算多少次加法;其中#define MAX(a,b) ((a)>(b)?(a):(b))c++

宏是基於「替換」工做的。segmentfault

這也就是所謂的「正則序」(參考SICP第一章),不斷地將宏名展開,反覆進行替換過程,直到無可替換。ide

也就是說,MAX(1+2,3)會被替換爲(1+2)>(3)?(1+2):(3)。而計算替換以後的表達式的值則需作2次加法。比起傳入函數max(int a,int b){return a>b?a:b;}內,多計算了一次加法。函數

解:spa

咱們能夠對整個表達式字符串左往右掃一次,對遇到的是啥字符分類討論。這也是我上一篇文章提到的「局部分離」的一種體現——先不看總體,先着眼局部——局部正確了,總體天然正確。這也就是所謂的分而治之(Divide & Conquer)。這種詳細列出每種情形的思想,也差很少能夠算做有限狀態機和所謂狀態轉移的思想。.net

這個題目,用遞歸作。我是這麼想的:code

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!

括號表達式解析就是要遞歸纔對吧!!!

……不過話說回來,我想出這個遞歸解法用了很久時間。我也是半路用上狀態思想的。

可是隻要想清楚每種狀態及其轉移,實現就如探囊取物了。

相關文章
相關標籤/搜索