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)
。作完這個步驟以後,這對括號內的表達式就解析完畢了。
新增狀態* 遇到逗號
,結果寄存器+=臨時寄存器
,臨時寄存器 = 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!
括號表達式解析就是要遞歸纔對吧!!!
……不過話說回來,我想出這個遞歸解法用了很久時間。我也是半路用上狀態思想的。
可是隻要想清楚每種狀態
及其轉移
,實現就如探囊取物了。