栈是一种后进先出的线性数据结构
只有一端可以进出元素

模板

  1. // tt表示栈顶,栈中第一个元素下标为1,便于处理边界情况
  2. int stk[N], tt = 0;
  3. // 向栈顶插入一个数
  4. stk[ ++ tt] = x;
  5. // 从栈顶弹出一个数
  6. tt -- ;
  7. // 栈顶的值
  8. stk[tt];
  9. // 判断栈是否为空
  10. if (tt > 0)
  11. {
  12. }
  13. // STL
  14. stack,
  15. size()
  16. empty()
  17. push() 向栈顶插入一个元素
  18. top() 返回栈顶元素
  19. pop() 弹出栈顶元素

题目

括号的匹配

这题属于带优先级的括号匹配问题
做法是为每个括号增加一个数表示优先级,push的时候进行检查

  1. #include <time.h>
  2. #include <iostream>
  3. #include <stack>
  4. #include <cstring>
  5. using namespace std;
  6. char str[300];
  7. int level(char op) { // 优先级,数字越小优先级越高
  8. if (op == '{') {
  9. return 1;
  10. }
  11. else if (op == '[') {
  12. return 2;
  13. }
  14. else if (op == '(') {
  15. return 3;
  16. }
  17. else if (op == '<') {
  18. return 4;
  19. }
  20. else {
  21. return -1;
  22. }
  23. }
  24. int main() {
  25. #ifdef SUBMIT
  26. freopen("in.txt", "r", stdin);
  27. freopen("out.txt", "w", stdout);
  28. long _begin_time = clock();
  29. #endif
  30. int n;
  31. cin >> n;
  32. while (n--) {
  33. stack<pair<char, int>> s;
  34. scanf("%s", str);
  35. int len = strlen(str);
  36. for (int i = 0; i < len; i++) {
  37. int lv = level(str[i]);
  38. if (s.empty()) {
  39. s.push({str[i], lv});
  40. }
  41. else {
  42. auto op = s.top();
  43. if ( // 匹配上
  44. (op.first == '{' && str[i] == '}')
  45. or (op.first == '[' && str[i] == ']')
  46. or (op.first == '(' && str[i] == ')')
  47. or (op.first == '<' && str[i] == '>')
  48. ) {
  49. s.pop();
  50. }
  51. else { // 满足优先级限制
  52. if (lv >= op.second) {
  53. s.push({str[i], lv});
  54. }
  55. else {
  56. break;
  57. }
  58. }
  59. }
  60. }
  61. if (s.empty()) { // 仍有括号没有匹配上
  62. printf("YES\n");
  63. }
  64. else {
  65. printf("NO\n");
  66. }
  67. }
  68. #ifdef SUBMIT
  69. long _end_time = clock();
  70. printf("\n\ntime = %ld ms", _end_time - _begin_time);
  71. #endif
  72. return 0;
  73. }

包含min函数的栈

这题在栈的基础上需要支持O(1)时间内查询最小值
小根堆只能做到O(logn)
如果只用一个变量记录最小值,出账时刚好弹出该最小值,那么需要重新遍历获得最小值
因此可以增加一个栈,用于保存原栈中从栈底到每个位置的最小值

  1. class MinStack {
  2. public:
  3. /** initialize your data structure here. */
  4. stack<int> st, st_min;
  5. MinStack() {
  6. }
  7. void push(int x) {
  8. st.push(x);
  9. if (st_min.size()) x = min(x, st_min.top());
  10. st_min.push(x);
  11. }
  12. void pop() {
  13. st.pop();
  14. st_min.pop();
  15. }
  16. int top() {
  17. return st.top();
  18. }
  19. int getMin() {
  20. return st_min.top();
  21. }
  22. };

编辑器

四种操作都在光标处发生,并且操作完成光标至多移动一个位置
这题与动态中位数的思想类似,用到了对顶栈
栈A存储从序列开头到光标,B存储从序列结尾到光标
查询最大前缀和仿照上一题,增加一个栈维护

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <limits.h>
  4. using namespace std;
  5. const int N = 1e6 + 10;
  6. int stl[N], str[N], tl, tr;
  7. int s[N], s_max[N];
  8. int q;
  9. void push_left(int x) {
  10. stl[++tl] = x;
  11. s[tl] = s[tl - 1] + x;
  12. s_max[tl] = max(s_max[tl - 1], s[tl]);
  13. }
  14. int main() {
  15. s_max[0] = INT_MIN;
  16. cin >> q;
  17. while (q--) {
  18. char op[2];
  19. scanf("%s", op);
  20. if (op[0] == 'I') {
  21. int x;
  22. scanf("%d", &x);
  23. push_left(x);
  24. }
  25. else if (op[0] == 'D') {
  26. if (tl > 0) tl--;
  27. }
  28. else if ('L' == op[0]) {
  29. if (tl > 0) str[++tr] = stl[tl--];
  30. }
  31. else if ('R' == op[0]) {
  32. if (tr > 0) push_left(str[tr--]);
  33. }
  34. else {
  35. int k;
  36. scanf("%d", &k);
  37. printf("%d\n", s_max[k]);
  38. }
  39. }
  40. return 0;
  41. }

进出栈序列问题

  • 法一
    • 搜索:对于任何一个状态,两种选择:
      • 下一个数进栈
      • 栈顶数出栈
    • 用递归进行枚举,复杂度O(2^N)
  • 法二
    • 动态规划:每个时刻我们只关心当前有多少个数未进栈,多少个数未出栈,与这些数具体是什么无关
    • 用F[i, j]表示i个数尚未进栈,j个数尚未出栈
    • 目标:F[N, 0]
    • 边界:F[0, 0] = 1
    • 转移方程:F[i, j] = F[i - 1, j + 1] + F[i, j - 1]
  • 法三
    • 递推:考虑“1”这个数在出栈序列中的位置为k
    • 序列的进出栈过程为:
      • 1入栈
      • 2~k进出栈
      • 1出栈
      • k + 1~N进出栈
    • 原问题就变成了k-1个数和N-k个数进出栈这两个子问题
    • 栈 - 图1
  • 法四
    • 该问题等价于求第N项卡特兰数,即栈 - 图2
    • 由于本题数据量较大,需要用质因数分解,将除法改成乘法,并且利用压位 ```cpp

      include

      include

using namespace std;

typedef long long LL;

const int N = 60000 * 2 + 10, M = 1e9; // 压位

int primes[N], cnt; bool st[N]; int powers[N];

void get_primes(int n) { // 线性筛法求质数 for (int i = 2; i <= n; i++) { if (!st[i]) primes[cnt++] = i; for (int j = 0; primes[j] <= n / i; j++) { st[primes[j] * i] = true; if (i % primes[j] == 0) break; } } }

int get(int n, int p) { // 求n!中的质数p的出现次数 int res = 0; while (n) { res += n / p; n /= p; } return res; }

void mul(vector &a, int b) { // 高精度乘法 + 压位 LL t = 0;

  1. for (int i = 0; i < a.size(); i++) {
  2. a[i] = a[i] * b + t;
  3. t = a[i] / M;
  4. a[i] %= M;
  5. }
  6. while (t) {
  7. a.push_back(t % M);
  8. t /= M;
  9. }

}

void out(vector &a) { // 输出,注意压位第一个单独输出,不要前导0 printf(“%lld”, a.back()); for (int i = a.size() - 2; i >= 0; i—) printf(“%09d”, a[i]); }

int main() { int n; cin >> n;

  1. get_primes(2*n);
  2. for (int i = 0; i < cnt; i++) { // 分子质数p数目-分母
  3. int p = primes[i];
  4. powers[p] = get(2 * n, p) - get(n, p) * 2;
  5. }
  6. int tmp = n + 1;
  7. for (int i = 0; i < cnt && primes[i] <= tmp; i++) { // 去掉n+1中质数数目
  8. int p = primes[i];
  9. while (tmp % p == 0) {
  10. tmp /= p;
  11. powers[p]--;
  12. }
  13. }
  14. vector<LL> res;
  15. res.push_back(1);
  16. for (int i = 0; i < cnt; i++) { // 质数幂次相乘
  17. int p = primes[i];
  18. for (int j = 0; j < powers[p]; j++) {
  19. mul(res, p);
  20. // out(res);
  21. // cout << endl;
  22. }
  23. }
  24. out(res);
  25. return 0;

}

  1. <a name="9BdpO"></a>
  2. # 单调栈
  3. 在栈的基础上维护这样一个性质:栈中数具备单调性<br />常见题型:找出每个数左边/右边离它最近的比它大/小的数
  4. <a name="IGlhy"></a>
  5. ## 模板
  6. ```cpp
  7. int tt = 0;
  8. for (int i = 1; i <= n; i ++ )
  9. { // 可以考虑预先向栈中Push一个最小/最大的数,作哨兵
  10. while (tt && check(stk[tt], i)) tt -- ; // 不满足单调性质就出栈
  11. stk[ ++ tt] = i;
  12. }

题目

直方图中最大的矩形

这题就是要找出当前矩阵左边/右边离它最近的高度比它底的矩阵
可以用两个单调栈分别维护左边和右边

  1. #include <iostream>
  2. #include <stack>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 100010;
  6. typedef long long LL;
  7. int n;
  8. int h[N], l[N], r[N];
  9. void get(int *rec) {
  10. stack<int> st;
  11. h[0] = -1; // 哨兵!
  12. st.push(0);
  13. for (int i = 1; i <= n; i++) {
  14. while (h[st.top()] >= h[i]) st.pop();
  15. rec[i] = st.top() + 1;
  16. st.push(i);
  17. }
  18. }
  19. int main() {
  20. while (cin >> n, n) {
  21. for (int i = 1; i <= n; i++) {
  22. scanf("%d", &h[i]);
  23. }
  24. get(l);
  25. reverse(h + 1, h + n + 1); // 逆序,求右边的转化成求左边的
  26. get(r);
  27. LL ans = 0;
  28. for (int i = 1, j = n; i <= n; i++, j--) {
  29. ans = max(ans, (LL)h[i] * (n - l[j] + 1 - r[i] + 1)); // 注意坐标转换,建议纸上演算!
  30. }
  31. printf("%lld\n", ans);
  32. }
  33. return 0;
  34. }