栈的应用
acwing150 括号画家
题目描述
达达是一名漫画家,她有一个奇特的爱好,就是在纸上画括号。
这一天,刚刚起床的达达画了一排括号序列,其中包含小括号 ( )、中括号 [ ] 和大括号 { },总长度为 N。
这排随意绘制的括号序列显得杂乱无章,于是达达定义了什么样的括号序列是美观的:
空的括号序列是美观的;
若括号序列 A 是美观的,则括号序列 ( A )、[ A ]、{ A } 也是美观的。
若括号序列 A、B 都是美观的,则括号序列 AB 也是美观的。
例如 [ ( ){ } ] ( ) 是美观的括号序列,而 ) ( { ) [ } ] ( 则不是。
现在达达想在她绘制的括号序列中,找出其中连续的一段,满足这段子串是美观的,并且长度尽量大。
你能帮帮她吗?
输入格式
输入一行由括号组成的字符串。
输出格式
输出一个整数,表示最长的美观的子段的长度。
数据范围
字符串长度不超过 105。
输入样例
1
| ({({(({()}})}{())})})[){{{([)()((()]]}])[{)]}{[}{)
|
输出样例
分析
这题是栈的应用,或是说栈的性质。
如何找出一个括号对?栈! 如果是左括号 ( [ { 就入栈,如果是右括号 ) ] } 并且栈顶元素为其相应的左括号,就出栈左括号;否则入栈右括号。我们来手搓一遍样例:
- ( { ( { ( ( { ( 全都是左括号,直接入栈。
- 读入右括号 ) 且栈顶元素为 ( ,出栈,序列为 ( { ( { ( ( {
- 读入右括号 } 且栈顶元素为 { ,出栈,序列为 ( { ( { ( (
- 读入右括号 } ,而栈顶元素为 ( ,入栈 }
对于一些细节问题与疑问,我们先来看代码
先上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<iostream> #include<stack> #include<cstring> using namespace std;
#define N 100010
int ans; char c[N]; stack<int> stk;
int main(){ scanf("%s",c); for(int i=0;i<strlen(c);i++){ char s=c[i]; if(s==')'&&stk.size()&&c[stk.top()]=='(') stk.pop(); else if(s=='}'&&stk.size()&&c[stk.top()]=='{') stk.pop(); else if(s==']'&&stk.size()&&c[stk.top()]=='[') stk.pop(); else stk.push(i); if(stk.size()!=0) ans=max(ans,i-stk.top()); else ans=max(ans,i+1); } cout<<ans<<endl; return 0; }
|
这段代码有几个细节要注意:
- 他的栈的类型为int,所以对应的是字符串 c 中的下标
- 因为用的是int,所以计算最大序列的方式是 i-stk.top() ,i为当前字符串下标,stk.top()是栈顶的字符串下标。比如 i = 6 时,前面的括号由于相互对应而出栈, stk.top() 为 3 ,res则为 3 。
- 对于栈的判空,当栈为空,无法返回栈顶元素,读入右括号时,就直接入栈;当栈无法返回栈顶元素,res就为 i + 1。
第二道
151. 表达式计算4 - AcWing题库
受不了了,又调好久,感觉是表达式求值的究极版。
分析
先不考虑括号,只考虑运算符。
首先,我们都知道。一个表达式可以用二叉树表达,也就是一个中缀表达式的中序遍历。要用一个迭代的方式来运算递归的式子,用栈是很方便的。说了这么多,也就是说,像第一道括号配对和表达式求值这样的题目,就用栈来做。
接下来,我们来思考一下如何才能让按照运算符有序地计算?因为优先级只有两种情况:1.比前面优先 2.前面优先。那么我们只要让优先级为单调递增,运算起来就会十分方便。实现方法就是当读入运算符时,与栈顶的运算符进行比较,如果优先级低于或等于栈顶的运算符,那就直接运算。
接下来再来考虑括号的情况。既然括号内的式子必须先计算,那么当遇到右括号,就一直进行向前的运算。因为此时的优先级为递增,所以从后往前算没毛病,直到遇到左括号停止。
小技巧:如何让栈内只剩下答案:可以在最右边额外加上一个‘ )’那么就可以将括号内剩余的值算完
最后,我们只剩下一些特殊情况要处理:
- 负号与减号混淆。负号如果是在式子首,即str[ 0 ]=’ - ‘,那就直接判定接下来的数字为负数。如果不是式子首,就要判断前面不是数字,不是‘ )’,那就是负号。否则是减号。
- 式子可能会有很多个右括号,可以在式子前加上许多 ( 来避免错误。左括号也没有什么影响。(题目坑人)
更多小细节可以细看代码,虽然看起来很长,其实内容并不复杂。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #include<iostream> #include<stack> #include<cstring> using namespace std;
stack<int> nums; stack<char> ops;
int qmi(int a,int k){ int res=1; while (k--) res*=a; return res; }
void cal(){ int a=nums.top();nums.pop(); int b=nums.top();nums.pop(); char c=ops.top();ops.pop(); int d; if (c=='+')d=b+a; else if (c=='-') d=b-a; else if (c=='*') d=b*a; else if (c=='/') d=b/a; else d=qmi(b,a);
nums.push(d); }
int main(){ string str; cin>>str; if(str[0]=='-') str='0'+str; string left; for(int i=0;i<str.size();i++) left+='('; str=left+str+')'; for(int i=0;i<str.size();i++){ if(str[i]>='0'&&str[i]<='9'){ int j=i,t=0; while(str[j]>='0'&&str[j]<='9'){ t=t*10+str[j]-'0'; j++; } i=j-1; nums.push(t); } else{ char c=str[i]; if (c=='(') ops.push(c); else if(c=='+'||c=='-'){ if (c =='-'&&i &&!(str[i - 1]>='0'&&str[i - 1]<='9')&&str[i-1]!=')'){ if (str[i + 1]=='(') { nums.push(-1); ops.push('*'); } else{ int j=i+1,t=0; while (str[j]>='0'&&str[j]<'9'){ t=t*10+str[j]-'0'; j++ ; } nums.push(-t); i=j-1; } } else{ while(ops.top()!='(') cal(); ops.push(c); } } else if (c=='*'||c=='/'){ while (ops.top()=='*'||ops.top()=='/'||ops.top()=='^') cal(); ops.push(c); } else if (c == '^'){ while(ops.top()=='^')cal(); ops.push(c); } else if (c == ')'){ while(ops.top()!='(') cal(); ops.pop(); } } } cout<<nums.top(); return 0; }
|
第三道
题目描述
152. 城市游戏 - AcWing题库
有一天,小猫 rainbow 和 freda 来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。
这片土地被分成 N×M 个格子,每个格子里写着 R
或者 F
,R
代表这块土地被赐予了 rainbow,F
代表这块土地被赐予了 freda。
现在 freda 要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着 F
并且面积最大。
但是 rainbow 和 freda 的 OI 水平都弱爆了,找不出这块土地,而蓝兔也想看 freda 卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为 S,它们将给你 3×S两银子。
输入格式
第一行包括两个整数 N,M,表示矩形土地有 N 行 M 列。
接下来 N 行,每行 M 个用空格隔开的字符 F
或 R
,描述了矩形土地。
每行末尾没有多余空格。
输出格式
输出一个整数,表示你能得到多少银子,即(3×最大 F
矩形土地面积)的值。
数据范围
1≤N,M≤1000
输入样例:
1 2 3 4 5 6
| 5 6 R F F F F F F F F F F F R R R F F F F F F F F F F F F F F F
|
输出样例:
分析
这题的做法与下面这题做法相似,只不过换成了字符串。只要用递推算出该点上面的连续矩形长,再遍历每一个点,时间复杂度为O(M*N)。
131. 直方图中最大的矩形 - AcWing题库
首先明确一点:一个矩形的延伸只会被比它矮的矩形卡住。
如上图,来看第 3 个矩形。以这个矩形为基础,向左延伸,然而 2 比它矮,所以不算入;向右延伸,4,5,6 比 3 高,可以截取与 3 等高部分算入面积。
所以接下来就很明确:要分别寻找矩形 i 左,右最近的比该矩形矮的矩形,存入l [ i ], r [ i ]。
先来看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include<iostream> #include<algorithm> using namespace std; #define N 1010 int n,m,res; int h[N][N]; char g[N][N]; int q[N],l[N],r[N]; void get(int a[],int l[]){ int tt=0; a[0]=-1; for(int i=1;i<=m;i++){ while(a[q[tt]]>=a[i]) tt--; l[i]=q[tt]+1; q[++tt]=i; } } int work(int a[]){ get(a,l); reverse(a+1,a+1+m); get(a,r); reverse(a+1,a+1+m); int res=0; for(int i=1;i<=m;i++){ int left=l[i]; int right=m+1-r[m+1-i]; res=max(res,a[i]*(right-left+1)); } return res; } int main(){ cin >> n >> m; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ char c; cin>>c; g[i][j]=c; if( c=='F') h[i][j]=h[i-1][j]+1; else h[i][j]=0; } } for(int i=1;i<=n;i++){ res=max(res,work(h[i])); } cout<<res*3<<endl; return 0; }
|
- reverse函数是将数组前后倒过来,为了方便一点求矩形 i 左,右最近的比该矩形矮的矩形。
但是不方便的一点是:因为求 r 时倒过来了,所以下标也是倒过来的,那么right要转换一下。