栈的应用
先来道开胃菜
题目描述
达达是一名漫画家,她有一个奇特的爱好,就是在纸上画括号。
这一天,刚刚起床的达达画了一排括号序列,其中包含小括号 ( )、中括号 [ ] 和大括号 { },总长度为 N。
这排随意绘制的括号序列显得杂乱无章,于是达达定义了什么样的括号序列是美观的:
空的括号序列是美观的;
若括号序列 A 是美观的,则括号序列 ( A )、[ A ]、{ A } 也是美观的。
若括号序列 A、B 都是美观的,则括号序列 AB 也是美观的。
例如 [ ( ){ } ] ( ) 是美观的括号序列,而 ) ( { ) [ } ] ( 则不是。
现在达达想在她绘制的括号序列中,找出其中连续的一段,满足这段子串是美观的,并且长度尽量大。
你能帮帮她吗?
输入格式
输入一行由括号组成的字符串。
输出格式
输出一个整数,表示最长的美观的子段的长度。
数据范围
字符串长度不超过 105。
输入样例
({({(({()}})}{())})})[){{{([)()((()]]}])[{)]}{[}{)
输出样例
4
简溪
这题是栈的应用,或是说栈的性质。
如何找出一个括号对?栈! 如果是左括号 ( [ { 就入栈,如果是右括号 ) ] } 并且栈顶元素为其相应的左括号,就出栈左括号;否则入栈右括号。我们来手搓一遍样例:
- ( { ( { ( ( { ( 全都是左括号,直接入栈。
- 读入右括号 ) 且栈顶元素为 ( ,出栈,序列为 ( { ( { ( ( {
- 读入右括号 } 且栈顶元素为 { ,出栈,序列为 ( { ( { ( (
- 读入右括号 } ,而栈顶元素为 ( ,入栈 }
对于一些细节问题与疑问,我们先来看代码
先上代码
#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。
第二道
受不了了,又调好久,感觉是表达式求值的究极版。
碱洗
先不考虑括号,只考虑运算符。
首先,我们都知道。一个表达式可以用二叉树表达,也就是一个中缀表达式的中序遍历。要用一个迭代的方式来运算递归的式子,用栈是很方便的。说了这么多,也就是说,像第一道括号配对和表达式求值这样的题目,就用栈来做。
接下来,我们来思考一下如何才能让按照运算符有序地计算?因为优先级只有两种情况:1.比前面优先 2.前面优先。那么我们只要让优先级为单调递增,运算起来就会十分方便。实现方法就是当读入运算符时,与栈顶的运算符进行比较,如果优先级低于或等于栈顶的运算符,那就直接运算。
接下来再来考虑括号的情况。既然括号内的式子必须先计算,那么当遇到右括号,就一直进行向前的运算。因为此时的优先级为递增,所以从后往前算没毛病,直到遇到左括号停止。
小技巧:如何让栈内只剩下答案:可以在最右边额外加上一个‘ )’那么就可以将括号内剩余的值算完
最后,我们只剩下一些特殊情况要处理:
- 负号与减号混淆。负号如果是在式子首,即str[ 0 ]=’ - ',那就直接判定接下来的数字为负数。如果不是式子首,就要判断前面不是数字,不是‘ )’,那就是负号。否则是减号。
- 式子可能会有很多个右括号,可以在式子前加上许多 ( 来避免错误。左括号也没有什么影响。(题目坑人)
更多小细节可以细看代码,虽然看起来很长,其实内容并不复杂。
代码
#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]=='(') { // 将-(...)变成-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;
}
第三道
题目描述
有一天,小猫 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
输入样例:
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
输出样例:
45
分析
这题的做法与下面这题做法相似,只不过换成了字符串。只要用递推算出该点上面的连续矩形长,再遍历每一个点,时间复杂度为O(M*N)。
首先明确一点:一个矩形的延伸只会被比它矮的矩形卡住。
如上图,来看第 3 个矩形。以这个矩形为基础,向左延伸,然而 2 比它矮,所以不算入;向右延伸,4,5,6 比 3 高,可以截取与 3 等高部分算入面积。
所以接下来就很明确:要分别寻找矩形 i 左,右最近的比该矩形矮的矩形,存入l [ i ], r [ i ]。
先来看代码
#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];//q为单调栈(存储的是下标),l为左边最近
void get(int a[],int l[]){
int tt=0;//tt为栈顶下标
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要转换一下。