栈
对于栈的基本特性,这里就不再赘述。着重讲解与栈有关的应用以及相关习题。
栈与卡特兰序列
关于卡特兰序列,我会在0x36节中详细介绍。
【例题】进出栈序列问题(AcWing130)
题目链接
思路: 细心观察我们会发现,进出栈序列符合卡特兰序列的特性,可以利用通项公式进行计算(将会在0x36节详细推导)。该通项公式为,第
N
N
N项卡特兰数为:
C
2
N
N
N
+
1
\frac{C_{2N}^N}{N + 1}
N+1C2NN。本题的核心就是计算这个卡特兰数,利用了质因子分解与高精度乘法进行求解。
AC代码:
#include<bits/stdc++.h>
#define N 120005
using namespace std;
int n;
int p[N],vis[N];
int power[N];
void prime(){
for(int i = 2;i <= 2 * n;i ++){
if(!vis[i])
p[++p[0]] = i;
for(int j = 1;j <= p[0] && p[j] * i <= 2 * n;j ++){
vis[i * p[j]] = 1;
if(i % p[j] == 0) break;
}
}
}
int getP(int x,int pp){
int res = 0;
while(x){
res += x / pp;
x /= pp;
}
return res;
}
void mul(vector<int>& a,int b){
int c = 0;
for(int i = 0;i < a.size();i ++){
a[i] = a[i] * b + c;
c = a[i] / 10000;
a[i] %= 10000;
}
while(c){
a.push_back(c % 10000);
c /= 10000;
}
}
void out(vector<int>& ans){
printf("%d",ans.back());
for(int i = ans.size() - 2;i >= 0;i --){
printf("%04d",ans[i]);
}
}
void solve(){
cin >> n;
prime();
for(int i = 1;i <= p[0];i ++){
power[p[i]] = getP(2 * n,p[i]) - 2 * getP(n,p[i]);
}
int tmp = n + 1;
for(int i = 1;i <= p[0] && p[i] <= n + 1;i ++){
while(tmp % p[i] == 0){
power[p[i]] --;
tmp /= p[i];
}
}
vector<int> ans;
ans.push_back(1);
for(int i = 1;i <= p[0];i ++){
for(int h = 0;h < power[p[i]];h ++){
mul(ans,p[i]);
}
}
out(ans);
cout << endl;
}
int main(){
solve();
return 0;
}
表达式计算
表达式一般有前缀,中缀与后缀表达式,这里主要讨论中缀表达式的计算方法。
我们可以使用两个栈,一个数字栈和一个运算符栈,步骤如下:
逐一扫描表达式中的每一个字符:
(1)如果遇到一个数字,压进数字栈中
(2)如果遇到左括号,把左括号压进运算符栈中
(3)如果遇到右括号,不断取出运算栈顶的一个运算符与数字栈栈顶两个运算符进行运算,把运算结果压进数字栈中,直到运算符栈顶为左括号为止。最后把左括号出栈。
(4)如果遇到运算符,只要运算符栈栈顶的优先级不低于当前扫描到的运算符,就不断取出栈顶运算符与两个数字栈栈顶数字进行运算,把运算结果压进数字栈中。最后把当前扫描到的运算符压进运算符栈中。
小技巧:我们可以为所求解的表达式子加上一对括号,这样最终数字栈中的元素就是表达式的结果。
【练习】表达式计算4(AcWing151)
思路:上面的步骤已经很详细啦。需要注意的是,这里可能会有多余的左右括号,我们只需要在表达式左边添加和表达式长度相同的左括号即可。想想为什么?
AC代码:
#include<bits/stdc++.h>
using namespace std;
stack<int> nums;
stack<char> ops;
void calc(){
char op = ops.top();ops.pop();
int b = nums.top();nums.pop();
int a = nums.top();nums.pop();
if(op == '+') nums.push(a + b);
else if(op == '-') nums.push(a - b);
else if(op == '*') nums.push(a * b);
else if(op == '/') nums.push(a / b);
else if(op == '^'){
int res = 1;
for(int i = 1;i <= b;i ++)
res *= a;
nums.push(res);
}
}
void solve(){
string s;
cin >> s;
string left;
for(int i = 0;i <= s.size();i ++) left += '(';
s = left + s + ')';
for(int i = 0;i < s.size();i ++){
if(s[i] >= '0' && s[i] <= '9'){
int num = 0;
int j = i;
while(s[j] >= '0' && s[j] <= '9'){
num = num * 10 + s[j] - '0';
j ++;
}
nums.push(num);
i = j - 1;
}
else{
char c = s[i];
if(c == '-' || c == '+'){
if(c == '-' && i && !(s[i - 1] >= '0' && s[i - 1] <= '9' || s[i - 1] == ')')){
nums.push(0);
}
while(ops.top() != '(') calc();
ops.push(c);
}
else if(c == '*' || c == '/'){
while(ops.top() != '(' && ops.top() != '+' && ops.top() != '-') calc();
ops.push(c);
}
else if(c == '^'){
while(ops.top() == '^') calc();
ops.push(c);
}
else if(c == '(') ops.push(c);
else{
while(ops.top() != '(') calc();
ops.pop();
}
}
}
cout << nums.top() << endl;
}
int main(){
solve();
return 0;
}
单调栈
单调栈在处理某些问题时,有非常大的用处。我们用两道题目来体会一下单调栈的应用。
【例题】直方图中最大的矩形(AcWing131)
题目链接
思路: 这是一道单调栈的经典例题。我们先来思考这样的一个问题,如果矩形的高度严格单调递增的话,那么我们应该如何计算最大的矩形面积呢?( 如下图)
我们只需要像下面这样,考虑把每一个矩形的高度作为最终矩形的高度,然后向右延伸(因为左边无法延伸),更新答案。
如果下一个矩形的高度小于当前矩形的高度,由于下一个矩形与之前矩形所组成的最大矩形的高度一定不超过下一个矩形本身的高度,那么在我们考虑完上面四种情况的矩形面积后,下图所示部分矩形已经对答案的更新没有用处了。
那么我们可以把这部分矩形删掉,用一个与最后一个矩形高度相同的矩形来替代。
在删除的时候,我们需要用即将删除掉的矩形更新答案。
小技巧:我们在矩形高度序列的最后,添加一个高度小于所有矩形高度的虚拟矩形,这样 最后就不需要再清空一遍栈啦!
AC代码:
#include<bits/stdc++.h>
#define N 100005
#define LL long long
using namespace std;
int h[N];
int top,stk[N];
int w[N];
int n;
void solve(){
for(int i = 1;i <= n;i ++)
cin >> h[i];
LL ans = 0;
h[n + 1] = 0;
for(int i = 1;i <= n + 1;i ++){
if(h[i] >= stk[top]){
stk[++ top] = h[i];
w[top] = 1;
}
else{
int width = 0;
while(top && stk[top] >= h[i]){
width += w[top];
ans = max(ans,1LL * width * stk[top]);
top --;
}
stk[++ top] = h[i];
w[top] = width + 1;
}
}
cout << ans << endl;
}
int main(){
while(cin >> n && n){
solve();
}
return 0;
}
【练习】城市游戏(AcWing152)
题目链接
思路:我们可以预处理出以每一个位置结尾的按列连续
F
F
F的长度,然后对于每一行,就转化成了上一题的问题,每一行都使用单调栈做一遍就可以啦!
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
char a[N][N];
int n,m;
int s[N][N];
int stk[N],w[N];
int top;
void solve(){
cin >> n >> m;
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= m;j ++)
cin >> a[i][j];
for(int j = 1;j <= m;j ++){
for(int i = 1;i <= n;i ++)
if(a[i][j] == 'R')
s[i][j] = 0;
else s[i][j] = s[i - 1][j] + 1;
}
int ans = 0;
for(int i = 1;i <= n;i ++){
top = 0;
s[i][m + 1] = 0;
for(int j = 1;j <= m + 1;j ++){
if(s[i][j] > stk[top]){
stk[++ top] = s[i][j],w[top] = 1;
}
else{
int width = 0;
while(top && s[i][j] <= stk[top]){
width += w[top];
ans = max(ans,width * stk[top]);
top --;
}
stk[++ top] = s[i][j],w[top] = width + 1;
}
}
}
cout << ans * 3 << endl;
}
int main(){
solve();
return 0;
}