理解:先进后出
基本知识:
(1)栈限定为只能在一端进行插入和删除操作;
(2)栈的实现只需要一个一维数组和一个指向栈顶的变量top。我们通过top来对栈进行插入和删除操作。
基本操作:
(1)初始化
top = 0;
(2)入栈
top ++;
s[top] = x; //假设需要入栈的字符暂存在字符变量x中
代码可简写为:
s[++ top] = x; //假设需要入栈的字符暂存在字符变量x中
(3)出栈
top --;
分类:
1.普通栈
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
//从栈顶弹出一个数
tt --;
//栈顶的值
stk[tt];
// 判断栈是否为空
if(tt > 0)
{
...
}
2.单调栈
*常见模型:找出每个数左边离它最近的比它大/小的数
模板:
int i;
int tt = 0;
for(i = 1 ; i <= n ; i ++)
{
while(tt > 0 && check(stk[tt], i)) tt --; //出栈
stk[ ++ tt] = i; //入栈
}
题目:
题目1(普通栈)(裸题)
实现一个栈,栈初始为空,支持四种操作:
push x
– 向栈顶插入一个数 x
- ;
pop
– 从栈顶弹出一个数;empty
– 判断栈是否为空;query
– 查询栈顶元素。
现在要对栈进行 M个操作,其中的每个操作 3 和操作 4都要输出相应的结果。
输入格式:
第一行包含整数 M,表示操作次数。接下来 M行,每行包含一个操作命令,操作命令为 push x
,pop
,empty
,query
中的一种。
输出格式:
对于每个 empty
和 query
操作都要输出一个查询结果,每个结果占一行。
其中,empty
操作的查询结果为 YES
或 NO
,query
操作的查询结果为一个整数,表示栈顶元素的值。
数据范围:
1≤M≤100000,1≤x≤109
输入样例:
10
push 5
query
push 6
pop
query
pop
empty
push 4
query
empty
输出样例:
5
5
YES
4
NO
AC代码1:(数组模拟)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int stk[N];
int tt;
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int i;
int m;
cin >> m;
while(m --)
{
string s;
cin >>s;
if(s == "push")
{
int x;
cin >> x;
stk[++ tt] = x;
}
if(s == "pop") tt --;
if(s == "query") cout << stk[tt] << endl;
if(s == "empty") cout << (tt == 0 ? "YES" : "NO") << endl;
}
return 0;
}
AC代码2:(STL)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
stack< int >stk;
int tt;
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int i;
int m;
cin >> m;
while(m --)
{
string s;
cin >>s;
if(s == "push")
{
int x;
cin >> x;
stk.push(x); // push函数在栈顶插入元素x
}
if(s == "pop") stk.pop(); // pop函数删除栈顶元素
if(s == "query") cout << stk.top() << endl; // top函数访问栈顶元素
if(s == "empty")
{
if(stk.empty()) // empty函数查询栈是否为空,如果为空,则返回真
{
cout << "YES" << endl;
}
else
{
cout << "NO" << endl;
}
}
}
return 0;
}
题目2(单调栈)(裸题)
题目描述:
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。第二行包含 N个整数,表示整数数列。
输出格式
共一行,包含 N个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。
数据范围
1≤N≤10^5,1≤数列中元素≤10^9
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int stk[N],tt = 0;
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int i;
int n;
cin >> n;
for(i = 0 ; i < n ; i ++)
{
int x;
cin >> x;
while(tt > 0 && stk[tt] >= x) tt --; // 比x大且在x的左边比然后不能输出出来,直接弹出
if(tt > 0) cout << stk[tt] << ' '; // 输出的是里x左边最近且比x小的数
else cout << "-1" << ' '; // 栈空的话代表没有元素比x小
stk[++ tt] = x; // 将刚才弹出的栈顶元素更新为更加小的(更优的)x
}
return 0;
}
题目3(单调栈)(简单题)
题目描述:
直方图是由在公共基线处对齐的一系列矩形组成的多边形。
矩形具有相等的宽度,但可以具有不同的高度。
例如,图例左侧显示了由高度为 2,1,4,5,1,3,3
的矩形组成的直方图,矩形的宽度都为 1
:
通常,直方图用于表示离散分布,例如,文本中字符的频率。
现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。
图例右图显示了所描绘直方图的最大对齐矩形。
输入格式
输入包含几个测试用例。
每个测试用例占据一行,用以描述一个直方图,并以整数 n
开始,表示组成直方图的矩形数目。
然后跟随 n个整数 h1,…,hn。
这些数字以从左到右的顺序表示直方图的各个矩形的高度。
每个矩形的宽度为 1。
同行数字用空格隔开。
当输入用例为 n=0时,结束输入,且该用例不用考虑。
输出格式:
对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。
每个数据占一行。
请注意,此矩形必须在公共基线处对齐。
数据范围
1≤n≤100000,0≤hi≤1000000000
输入样例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出样例:
8
4000
思路:
1.若暴力
首先考虑暴力做法,以每个矩形的高度为准,向两边扩展,直到遇到比它矮的为止
如图所示,记录每个矩形向两侧扩展的边界 [l,r]
它扩展出的矩形面积为 s=(r−l+1)∗h
最优解会在这些扩展出的矩形中产生。
时间复杂度 为(n^2):
每个矩形向两侧扩展的最大宽度为矩形个数 n,共进行 n 次这样的操作
2.单调栈优化
在计算每个矩形可以扩展的左边界时,可以发现有一些矩形是可以不考虑的
如图所示,由于2号矩形的存在,在计算2右边的矩形的左边界时,可以不考虑1号矩形。
高度高于2号的矩形会被2卡住,高度小于等于2号的也必然小于等于1号。
观察可知,在计算左边界时,靠左的且较高的矩形可以省略,因此可以用单调栈优化。
q[tt]作为栈顶元素下标,当满足h[q[tt]] >= h[i]时,弹出栈顶
时间复杂度 为O(n)
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int h[N],q[N],l[N],r[N];
int main()
{
int i;
int n;
while(cin >> n && n != 0)
{
for(i = 1; i <= n; i ++) cin>>h[i];
h[0]=h[n+1]=-1;
int tt=-1;
q[++ tt]=0;
for(i = 1 ; i <= n ; i ++)
{
while(h[q[tt]] >= h[i]) tt--;
l[i] = i - q[tt];
q[++ tt] = i;
}
tt=0;
q[0] = n+1;
for(i = n ; i ; i --)
{
while(h[q[tt]] >= h[i]) tt--;
r[i] = q[tt]-i;
q[++ tt] = i;
}
ll res = 0;
for(i = 1;i <= n ; i ++) res = max(res,(ll)h[i]*(l[i] + r[i] - 1));
cout<<res<<endl;
}
return 0;
}
题目4(单调栈)(简单题)
题目描述
You are going to implement the most powerful editor for integer sequences.
The sequence is empty when the editor is initialized.
There are 5 types of instructions.
- I x Insert x after the cursor.
- D Delete the element before the cursor.
- L Move the cursor left unless it has already been at the begin.R Move the cursor right unless it has already been at the end.
- Q k Suppose that the current sequence BEFORE the cursor is {a1,a2…an}\{ a_1,a_2\dots a_n \}{a1,a2…an}. Find max1≤i≤kSimax_{1\leq i\leq k} S_imax1≤i≤kSi where S;=a1+a2+⋅⋅+a¡S_; = a_1 +a_2+··+ a_¡S;=a1+a2+⋅⋅+a¡
输入描述
The input file consists of multiple test cases. For each test case:
The first line contains an integer Q, which is the number of instructions. The next Q lines contain an instruction asdescribed above.
(1<Q<1061 \lt Q \lt 10^61<Q<106,∣x[<103|x[ \lt10^3∣x[<103 for I instruction, 1<k<n1 \lt k\lt n1<k<n. for Q instruction)
输出描述
For each Q instruction, output the desired value.
示例1
输入
8 I 2 I -1 I 1 Q 3 L D R Q 2
输出
2 3
说明:
The following diagram shows the status of sequence after each instruction:
1 ≤ Q ≤ 10^6
∣x∣ ≤ 10^3
1 ≤ k ≤ n
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int stk[N],p[N];
char str[2];
int l,r,stkl[N],stkr[N];
void push(int i)
{
stkl[++ l] = i;
stk[l] = stk[l - 1] + i;
p[l] = max(p[l - 1],stk[l]);
}
int main()
{
int n,k;
cin >> n;
p[0] = INT_MIN;
while(n --)
{
cin >> str;
if(str[0] == 'I')
{
cin>>k;
push(k);
}
if(str[0] == 'D')
{
if(l > 0) l--;
}
if(str[0] == 'L')
{
if(l > 0) stkr[++r] = stkl[l--];
}
if(str[0] == 'R')
{
if(r > 0) push(stkr[r--]);
}
if(str[0] == 'Q')
{
cin >> k;
cout << p[k] << endl;
}
}
return 0;
}
题目5(单调栈)(中档题)
有一天,小猫 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
思路:
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
int s[N][N], l[N], r[N]; //h表示每个矩形高度,l和r分别表示矩形能向左右两侧扩展的边界
int q[N]; //q储存单调栈
int n,m;
int work(int h[]) //传入直方图的高,这个函数就是131题的原代码
{
int i;
h[0] = h[m + 1] = -1; //保证矩形左右两侧一定有小于它高度的矩形,即保证栈非空
//可省去判断条件tt >= 0
//求左边界
int tt = 0; //tt表示栈顶
q[0] = 0;
for (i = 1; i <= m ; i ++ )
{
while (h[q[tt]] >= h[i]) tt -- ; //往左找到第一个比h[i]小的位置为止
l[i] = q[tt]; //记录第一个比h[i]小的矩形的位置
q[ ++ tt] = i; //添加当前矩形到栈中
}
//求右边界
tt = 0;
q[0] = m + 1;
for (i = m; i ; i -- )
{
while (h[q[tt]] >= h[i]) tt -- ;
r[i] = q[tt];
q[ ++ tt] = i;
}
int res = 0;
for (i = 1; i <= m; i ++ )
res = max(res, h[i] * (r[i] - l[i] - 1)); //更新矩形最大值
return res;
}
int main()
{
int i;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ )
{
char c;
cin >> c;
if (c == 'F') s[i][j] = s[i - 1][j] + 1; //累计F及其上方一共有几个F
}
}
int res = 0;
for (i = 1; i <= n; i ++ ) res = max(res, work(s[i])); //当下边界为i时,计算最大面积
cout << res * 3 << endl;
return 0;
}