1:中綴表達式的值
- 總時間限制:
- 200ms
- 內存限制:
- 1024kB
- 描述
-
人們熟悉的四則運算表達式稱為中綴表達式,例如(23+34*45/(5+6+7))。在程序設計語言中,可以利用堆棧的方法把中綴表達式轉換成保值的后綴表達式(又稱逆波蘭表示法),並最終變為計算機可以直接執行的指令,得到表達式的值。
給定一個中綴表達式,編寫程序,利用堆棧的方法,計算表達式的值。 - 輸入
-
第一行為測試數據的組數N
接下來的N行,每行是一個中綴表達式。表達式中只含數字、四則運算符和圓括號,操作數都是正整數,數和運算符、括號之間沒有空格。中綴表達式的字符串長度不超過600。 - 輸出
- 對每一組測試數據輸出一行,為表達式的值
- 樣例輸入
-
3 3+5*8 (3+5)*8 (23+34*45/(5+6+7))
- 樣例輸出
-
43 64 108
- 提示
-
注意:運算過程均為整數運算(除法運算'/'即按照C++定義的int除以int的結果,測試數據不會出現除數為0的情況),輸出結果也為整數(可能為負)。
中間計算結果可能為負。
- 這題有點兒變態qaq
解題思路是:中綴表達式-->后綴表達式-->計算后綴表達式
step 1: 中綴表達式-->后綴表達式
- 中綴表達式a + b*c + (d * e + f) * g,其轉換成后綴表達式則為a b c * + d e * f + g * +。
- 轉換過程需要用到棧,具體過程如下:
- 1)如果遇到操作數,我們就直接將其輸出。
- 2)如果遇到操作符,則我們將其放入到棧中,遇到左括號時我們也將其放入棧中。
- 3)如果遇到一個右括號,則將棧元素彈出,將彈出的操作符輸出直到遇到左括號為止。注意,左括號只彈出並不輸出。
- 4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,從棧中彈出元素直到遇到發現更低優先級的元素(或者棧為空)為止。彈出完這些元素后,才將遇到的操作符壓入到棧中。有一點需要注意,只有在遇到" ) "的情況下我們才彈出" ( ",其他情況我們都不會彈出" ( "。
- 5)如果我們讀到了輸入的末尾,則將棧中所有元素依次彈出。
step 2:計算后綴表達式
- 后綴表達式的求值過程為,從左到右掃描后綴表達式:
- 1)如果遇到操作數,將其壓入棧中。
- 2)如果遇到操作符,則從棧中彈出兩個操作數,計算結果,然后把結果入棧。
- 3)直到遍歷完后綴表達式,則計算完成,此時的棧頂元素即為計算結果。
我的代碼思路
- 我在做的時候為了簡化代碼,直接兩大步驟並為一大步驟了,實現思路不變。
- 具體來說,我用了一個數字棧和一個符號棧來分別儲存操作數和操作符;在遇到step1中(3)(4)(5)的“輸出/彈出操作符”的情況時,實施類似step2中(2)的做法:從數字棧中彈出兩個操作數,從符號棧中彈出一個操作符,計算這三者的運算結果,把結果放回數字棧中;最終,符號棧應該為空,數字棧應該只剩下一個元素,也就是表達式的值,其余情況均為錯誤輸入。
我的代碼

1 #include <cstdio>
2 #include <stack>
3 #include <cstring>
4 using namespace std; 5
6 //判斷運算符號優先級是否滿足c1>=c2
7 bool prior(const char c1, const char c2){ 8 if((c1=='-'||c1=='+') && (c2=='*'||c2=='/')) 9 return false; 10 else
11 return true; 12 } 13
14 //函數Op2Num(): 操作兩個數字的運算;
15 void Op2Num(stack<int> & num, stack<char> & op){ 16 int num1, num2;//取數字棧頂的兩個操作數
17 num1 = num.top(); num.pop(); 18 num2 = num.top(); num.pop(); 19 switch(op.top()){//操作數字棧頂兩數字並重新入棧
20 case '+': num.push(num2+num1); break; 21 case '-': num.push(num2-num1); break; 22 case '*': num.push(num2*num1); break; 23 case '/': num.push(num2/num1); break; 24 default: printf(" something wrong in Op2Num\n"); 25 } 26 op.pop();//彈出符號棧頂符號
27 } 28
29 //函數OpeNum(): 30 //cur傳入字符')','+','-','*','/',表明調用本函數的觸發條件
31 void OpeNum(stack<int> & num, stack<char> & op, char cur){ 32 while(!op.empty() && op.top()!='(' && prior(op.top(), cur)) 33 Op2Num(num, op); 34 switch(cur){ 35 case ')': 36 if(op.top() == '(') 37 op.pop();//彈左括號
38 break; 39 default: 40 op.push(cur);//當前符號入符號棧
41 } 42 return; 43 } 44
45 //從字符串中取數字
46 int GetNum(char * str, int & i){ 47 int temp = 0; 48 while(str[i]>='0' && str[i]<='9'){ 49 temp = temp*10 + str[i] - '0'; 50 ++i; 51 } 52 --i; 53 return temp; 54 } 55
56 //表達式結束,所有符號彈棧
57 void PopAll(stack<int> & num, stack<char> & op){ 58 while(!op.empty()) 59 Op2Num(num, op); 60 return; 61 } 62
63 //中綴表達式運算
64 int CalNifix(char * str){ 65 stack<int> num;//數字棧
66 stack<char> op;//符號棧
67 for(int i=0; str[i]; ++i){ 68 switch(str[i]){ 69 case '(': 70 op.push('('); break;//左括號入棧
71 case ')': 72 case '+': 73 case '-': 74 case '*': 75 case '/': 76 OpeNum(num, op, str[i]); break;//操作運算
77 default: 78 num.push(GetNum(str, i));//取數字入數字棧
79 } 80 } 81 PopAll(num, op);//表達式結束,所有符號彈棧
82 return num.top();//返回運算結果
83 } 84
85 int main() { 86 int T; 87 scanf("%d", &T); 88 while(T--){ 89 char str[650];//儲存中綴表達式
90 scanf("%s", str); 91 printf("%d\n", CalNifix(str)); 92 } 93 return 0; 94 }
我的代碼是按照自己的思路寫的,大家當然可以把step1和step2分開做、依次做,我想可能會更容易理解,結構也更清晰。
但是無論如何,請一定一定不要直接copy,一定一定要自己看懂思路后,獨立寫一遍,這樣才能學到東西嘛!加油!
2:滑動窗口
- 總時間限制:
- 12000ms
- 內存限制:
- 65536kB
- 描述
-
給定一個長度為n(n<=10^6)的數組。有一個大小為k的滑動窗口從數組的最左端移動到最右端。你可以看到窗口中的k個數字。窗口每次向右滑動一個數字的距離。下面是一個例子:數組是 [1 3 -1 -3 5 3 6 7], k = 3。
窗口位置 最小值 最大值 [1 3 -1] -3 5 3 6 7 -1 3 1 [3 -1 -3] 5 3 6 7 -3 3 1 3 [-1 -3 5] 3 6 7 -3 5 1 3 -1 [-3 5 3] 6 7 -3 5 1 3 -1 -3 [5 3 6] 7 3 6 1 3 -1 -3 5 [3 6 7] 3 7
- 你的任務是得到滑動窗口在每個位置時的最大值和最小值。
- 輸入
-
輸入包括兩行。
第一行包括n和k,分別表示數組的長度和窗口的大小。
第二行包括n個數字。 - 輸出
-
輸出包括兩行。
第一行包括窗口從左至右移動的每個位置的最小值。
第二行包括窗口從左至右移動的每個位置的最大值。 - 樣例輸入
-
8 3 1 3 -1 -3 5 3 6 7
- 樣例輸出
-
-1 -3 -3 -3 3 3 3 3 5 5 6 7
曲折歷程
- 啊,這是一道看似簡單的題目,做了好久才AC。
- 第一想法是暴力查找,代碼簡單然鵝TLE了,哇地一聲哭出來(不是
- 為了讓這個暴力代碼更簡潔,我甚至還學了min_element()和max_element()函數;這兩個函數當然是很有用的,可是我用錯了位置,不過還是打算記錄一下
/*這是一段偽代碼,用於指示函數用法*/
#include <algorithm>
std::min_element
default (1)
template <class ForwardIterator>
ForwardIterator min_element (ForwardIterator first, ForwardIterator last);
custom (2)
template <class ForwardIterator, class Compare>
ForwardIterator min_element (ForwardIterator first, ForwardIterator last,Compare comp);
std::max_element
default (1)
template <class ForwardIterator>
ForwardIterator max_element (ForwardIterator first, ForwardIterator last);
custom (2)
template <class ForwardIterator, class Compare>
ForwardIterator max_element (ForwardIterator first, ForwardIterator last,Compare comp);
程序如下

1 #include <cstdio>
2 #include <algorithm>
3 using namespace std; 4
5 int main(){ 6 int n, k; 7 int t[1000010]; 8 scanf("%d %d", &n, &k); 9 for(int i=0; i<n; ++i) 10 scanf("%d", t+i); 11 for(int i=k; i<=n; ++i) 12 printf("%d ",* min_element(t+i-k, t+i)); 13 printf("\n"); 14 for(int i=k; i<=n; ++i) 15 printf("%d ",* max_element(t+i-k, t+i)); 16 printf("\n"); 17 return 0; 18 }
。。。
我天真地以為把min_element()和max_element()函數換成for循環就會省時間,畢竟類比一下:用memset()給數組清零比for循環清零要慢許多,於是產生了下面的程序

1 #include <cstdio>
2 #include <algorithm>
3 using namespace std; 4
5 int main(){ 6 int n, k, t_min, t_max; 7 int t[1000010], m[1000010]; 8 scanf("%d %d", &n, &k); 9 for(int i=0; i<n; ++i) 10 scanf("%d", t+i); 11 for(int i=k; i<=n; ++i){ 12 t_min = t_max = t[i-k]; 13 for(int j=i-k+1; j<i; ++j){ 14 t_min = min(t_min, t[j]); 15 t_max = max(t_max, t[j]); 16 } 17 m[i] = t_max; 18 printf("%d ", t_min); 19 } 20 printf("\n"); 21 for(int i=k; i<=n; ++i) 22 printf("%d ", m[i]); 23 printf("\n"); 24 return 0; 25 }
不出預料地再次TLE
正經答案
最終我向度娘屈服了,查了一下,原來大家都是用單調隊列實現解決滑動窗口問題的,貼幾個講得比較詳細的帖子鏈接,因為我懶hh
1.https://www.acwing.com/solution/acwing/content/2499/
2.https://www.cnblogs.com/llke/p/10780121.html
我自己寫的代碼如下:

1 #include <iostream>
2 using namespace std; 3
4 #define N 1000010
5
6 //輸出滑動窗口最小值
7 void PrintMin(int * num, const int n, const int k){ 8 //p_min[head...tail]儲存當前窗口內的單調序列
9 int p_min[n], head=0, tail=-1; 10 for(int i=0; i<n; ++i){ 11 while(head<=tail && num[i]<=num[p_min[tail]]) 12 --tail;//隊尾大元素元素出隊
13 p_min[++tail] = i;//元素i入隊尾
14 while(p_min[head]<=i-k) 15 ++head;//隊頭窗口外元素出隊
16 if(i>=k-1)//輸出當前窗口最小值
17 cout << num[p_min[head]] << ' '; 18 } 19 cout << endl; 20 } 21
22 //輸出滑動窗口最大值
23 void PrintMax(int * num, const int n, const int k){ 24 int p_max[n], head=0, tail=-1; 25 for(int i=0; i<n; ++i){ 26 while(head<=tail && num[i]>=num[p_max[tail]]) 27 --tail; 28 p_max[++tail] = i; 29 while(p_max[head]<=i-k) 30 ++head; 31 if(i>=k-1) 32 cout << num[p_max[head]] << ' '; 33 } 34 cout << endl; 35 } 36
37 int main() { 38 int n, k, num[N]; 39 cin >> n >> k; 40 for(int i=0; i<n; ++i) 41 cin >> num[i]; 42 PrintMin(num, n, k); 43 PrintMax(num, n, k); 44 return 0; 45 }
觀察發現,PrintMin()和PrintMax()函數的重復之處過多,唯一的差別在於“--tail”的時候是“隊尾大元素出隊”還是“隊尾小元素出隊”。很自然地想到,我們可以利用函數指針傳遞一個比較函數,以簡化程序、合二為一。代碼如下:

1 #include <iostream>
2 using namespace std; 3
4 #define N 1000010
5
6 bool MyMin(int a, int b){return a<=b;} 7 bool MyMax(int a, int b){return a>=b;} 8
9 void Print(int * num, const int n, const int k, bool (*f)(int,int)){ 10 //p[head...tail]儲存當前窗口內的單調序列
11 int p[n], head=0, tail=-1; 12 for(int i=0; i<n; ++i){ 13 while(head<=tail && f(num[i],num[p[tail]])) 14 --tail;//隊尾元素出隊
15 p[++tail] = i;//元素i入隊尾
16 while(p[head]<=i-k) 17 ++head;//隊頭窗口外元素出隊
18 if(i>=k-1)//輸出當前窗口最值
19 cout << num[p[head]] << ' '; 20 } 21 cout << endl; 22 } 23
24 int main() { 25 int n, k, num[N]; 26 cin >> n >> k; 27 for(int i=0; i<n; ++i) 28 cin >> num[i]; 29 Print(num, n, k, MyMin); 30 Print(num, n, k, MyMax); 31 return 0; 32 }
后來又想到,可以通過把cin-cout換成scanf-printf,以及在函數前加上inline字樣來減少運行用時,代碼如下:

1 #include <stdio.h> 2 using namespace std; 3 4 #define N 1000010 5 6 bool MyMin(int a, int b){return a<=b;} 7 bool MyMax(int a, int b){return a>=b;} 8 9 inline void Print(int * num, const int n, const int k, bool (*f)(int,int)){ 10 //p[head...tail]儲存當前窗口內的單調序列 11 int p[n], head=0, tail=-1; 12 for(int i=0; i<n; ++i){ 13 while(head<=tail && f(num[i],num[p[tail]])) 14 --tail;//隊尾元素出隊 15 p[++tail] = i;//元素i入隊尾 16 while(p[head]<=i-k) 17 ++head;//隊頭窗口外元素出隊 18 if(i>=k-1)//輸出當前窗口最值 19 printf("%d ", num[p[head]]); 20 } 21 printf("\n"); 22 } 23 24 int main() { 25 int n, k, num[N]; 26 scanf("%d %d", &n, &k); 27 for(int i=0; i<n; ++i) 28 scanf("%d", num+i); 29 Print(num, n, k, MyMin); 30 Print(num, n, k, MyMax); 31 return 0; 32 }
滑動窗口1.0、2.0、3.0(從上到下)的OJ運行測試結果如下:
可以看到它們在運行效率上有了一些提升,而且我還學到了不少新東西,雖然花了很多時間,但還是好開星!!!
3:棧的基本操作
- 總時間限制:
- 1000ms
- 內存限制:
- 1000kB
- 描述
-
棧是一種重要的數據結構,它具有push k和pop操作。push k是將數字k加入到棧中,pop則是從棧中取一個數出來。
棧是后進先出的:把棧也看成橫向的一個通道,則push k是將k放到棧的最右邊,而pop也是從棧的最右邊取出一個數。
假設棧當前從左至右含有1和2兩個數,則執行push 5和pop操作示例圖如下:
-
push 5 pop
棧 1 2 -------> 1 2 5 ------> 1 2
現在,假設棧是空的。給定一系列push k和pop操作之后,輸出棧中存儲的數字。若棧已經空了,仍然接收到pop操作,
則輸出error。
-
- 輸入
-
第一行為m,表示有m組測試輸入,m<100。
每組第一行為n,表示下列有n行push k或pop操作。(n<150)
接下來n行,每行是push k或者pop,其中k是一個整數。
(輸入保證同時在棧中的數不會超過100個) - 輸出
- 對每組測試數據輸出一行。該行內容在正常情況下,是棧中從左到右存儲的數字,數字直接以一個空格分隔,如果棧空,則不作輸出;但若操作過程中出現棧已空仍然收到pop,則輸出error。
- 樣例輸入
-
2 4 push 1 push 3 pop push 5 1 pop
- 樣例輸出
-
1 5 error
我的答案

1 #include <stdio.h>
2 #include <stack>
3 using namespace std; 4
5 //函數:操作棧
6 void OpStack(){ 7 stack<int> st; 8 int n, t; 9 char cmd[5]; 10 bool flag=1; 11 scanf("%d", &n); 12 while(n--){ 13 scanf("%s", cmd); 14 if(cmd[1] == 'u'){//push
15 scanf("%d", &t); 16 st.push(t); 17 } 18 else{//pop
19 if(!st.empty()) 20 st.pop(); 21 else//error
22 flag = 0; 23 } 24 } 25 if(flag){//從棧底向棧頂輸出,要倒一下
26 stack<int> _st; 27 while(!st.empty()){ 28 _st.push(st.top()); 29 st.pop(); 30 } 31 while(!_st.empty()){ 32 printf("%d ", _st.top()); 33 _st.pop(); 34 } 35 } 36 else
37 printf("error"); 38 printf("\n"); 39 return; 40 } 41
42 int main() { 43 int m; 44 scanf("%d",&m); 45 while(m--) 46 OpStack(); 47 return 0; 48 }
這題比較簡單~
我去寫lab了,一起加油w