zoukankan      html  css  js  c++  java
  • [算法学习] 单调栈

    单调栈在子矩阵方面的应用

    大致的题型
    给定你一个(n imes m)的矩形,询问有多少个子矩阵,使得这个子矩阵满足一定的条件。

    类型1

    题目链接:洛谷P1950 长方形

    Description

    给定你一个(n imes m)的矩阵,询问有多少个内部全是(1)的子矩阵。
    数据范围(1le n,mle 1000)

    Solution

    先想如何暴力。
    枚举这个子矩阵的两长和两宽,在暴力(check)该子矩阵内的所有点是否为(1)
    复杂度:(O(n^6)),期望得分:(10)分。
    我们发现,这个(check)部分可以利用二维前缀和来优化,因此可以预处理(cnt[i][j]=sum_{p=1}^{i}sum_{q=1}^{j}a_{i,j})
    复杂度:(O(n^4)),期望得分:(30)分。
    考虑框定了该子矩形的左端右端,那么我们可以扫,并进行简单计数即可。
    复杂度:(O(n^3)),期望得分:(30)~(100)分。
    接下来,我们用:
    (h_{i,j})表示((i,j))这个点往上连续(1)的最长长度。
    (l_{i,j})表示从((i,j))开始第一个满足(h_{i,l_{i,j}}le h(i,j))的点,如果不存在,令(l_{i,j}=0)
    (r_{i,j})表示从((i,j))开始第一个满足(h_{i,r_{i,j}}le h(i,j))的点,如果不存在,令(r_{i,j}=m+1)
    那么,对于((i,j))为矩形底的贡献,就是(val=(j-l_{i,j})*(r_{i,j}-j)*h_{i,j})
    考虑一下,这样做如何保证答案不重不漏。
    不重:当且仅当在同一行存在两个数(l_{i,j_1}=l_{i,j_2})并且(r_{i,j_1}=r_{i,j_2})的时候,才有可能算重矩形。
    但是这种情况是不存在的,因为(l_{i,j})满足了左边第一个小于等于的,右边第一个小于的,显然无法构造出这种情况。
    不漏:对于一个矩形,总有一个(l_{i,j},r_{i,j})能框住一个矩形的两边,故这个矩形一定能被计算到。
    我们可以通过一个单调栈来计算(l_{i,j})(r_{i,j}),复杂度(O(n^2))

    Code

    #pragma GCC optimize(2)
    #pragma GCC optimize(3)
    #include <bits/stdc++.h>
    using namespace std;
    
    #define rint register int
    const int N = 1005;
    bool a[N][N];
    int h[N][N], l[N][N], r[N][N], n, m;
    
    stack <int> st;
    
    void push_l(int i, int j) {
      while (!st.empty() && h[i][st.top()] > h[i][j]) r[i][st.top()] = j, st.pop();
      st.push(j);
    }
    void push_r(int i, int j) {
      while (!st.empty() && h[i][st.top()] >= h[i][j]) l[i][st.top()] = j, st.pop();
      st.push(j);
    }
    
    int main() {
      scanf("%d%d", &n, &m);
      for (rint i = 1; i <= n; i++) {
        for (rint j = 1; j <= m; j++) {
          char x = getchar();
          while (x != '.' && x != '*') {
            x = getchar();
          }
          a[i][j] = x == '.';
        }
      }
      for (rint j = 1; j <= m; j++) {
        for (rint i = 1; i <= n; i++) {
          if (a[i][j]) h[i][j] = h[i - 1][j] + 1;
          else h[i][j] = 0;
        }
      }
      long long ans = 0ll;
      for (rint i = 1; i <= n; i++) {
        while (!st.empty()) st.pop();
        for (rint j = 1; j <= m; j++) {
          push_l(i, j);
        } 
        while (!st.empty()) r[i][st.top()] = m + 1, st.pop();
        for (rint j = m; j >= 1; j--) {
          push_r(i, j);
        }
        while (!st.empty()) l[i][st.top()] = 0, st.pop();
        for (rint j = 1; j <= m; j++) {
          ans += 1ll * (j - l[i][j]) * (r[i][j] - j) * h[i][j];
        }
      }
      printf("%lld
    ", ans);
      return 0;
    }
    

    类型2

    题目链接:ZJOI2007 棋盘制作

    Description

    给定你一个(n imes m)的矩阵,询问最大的全(1)矩形和正方形的面积。
    数据范围(1le n,mle 2000)

    Solution

    跟上一题类似,我们用:
    (h_{i,j})表示((i,j))这个点往上连续(1)的最长长度。
    (l_{i,j})表示从((i,j))开始第一个满足(h_{i,l_{i,j}}le h(i,j))的点,如果不存在,令(l_{i,j}=0)
    (r_{i,j})表示从((i,j))开始第一个满足(h_{i,r_{i,j}}le h(i,j))的点,如果不存在,令(r_{i,j}=m+1)
    那么,对于((i,j))为矩形底的贡献,我们需要求的是 (max{(r_{i,j}-l_{i,j}-1) imes h_{i,j}})
    正方形的话,只需要求(max^2 {min{r_{i,j}-l_{i,j}-1,h_{i,j}}})

    Code

    #pragma GCC optimize(2)
    #pragma GCC optimize(3)
    #include <bits/stdc++.h>
    using namespace std;
    
    #define rint register int
    #define ll long long
    
    const int N = 2105;
    bool a[N][N];
    int h[N][N], l[N][N], r[N][N], n, m;
    
    stack <int> st;
    
    void push_l(int i, int j) {
      while (!st.empty() && h[i][st.top()] > h[i][j]) r[i][st.top()] = j, st.pop();
      st.push(j);
    }
    void push_r(int i, int j) {
      while (!st.empty() && h[i][st.top()] >= h[i][j]) l[i][st.top()] = j, st.pop();
      st.push(j);
    }
    
    pair <ll, ll> solve() {
      for (rint j = 1; j <= m; j++) {
        for (rint i = 1; i <= n; i++) {
          if (a[i][j]) h[i][j] = h[i - 1][j] + 1;
          else h[i][j] = 0;
          l[i][j] = r[i][j] = 0;
        }
      }
      long long ans1 = 0ll, ans2 = 0ll;
      for (rint i = 1; i <= n; i++) {
        while (!st.empty()) st.pop();
        for (rint j = 1; j <= m; j++) {
          push_l(i, j);
        } 
        while (!st.empty()) r[i][st.top()] = m + 1, st.pop();
        for (rint j = m; j >= 1; j--) {
          push_r(i, j);
        }
        while (!st.empty()) l[i][st.top()] = 0, st.pop();
        for (rint j = 1; j <= m; j++) {
          ans1 = max(ans1, 1ll * (r[i][j] - l[i][j] - 1) * h[i][j]);
          ans2 = max(ans2, (long long)min(r[i][j] - l[i][j] - 1, h[i][j]));
        }
      }
      return make_pair(ans1, ans2 * ans2);
    }
    
    int main() {
      scanf("%d%d", &n, &m);
      for (rint i = 1; i <= n; i++) {
        for (rint j = 1; j <= m; j++) {
          scanf("%d", &a[i][j]);
          if ((i + j) & 1) {
            a[i][j] ^= 1;
          }
        }
      }
      pair <ll, ll> ans1 = solve();
      for (rint i = 1; i <= n; i++) {
        for (rint j = 1; j <= m; j++) {
          a[i][j] ^= 1;
        }
      }
      pair <ll, ll> ans2 = solve();
      
      printf("%lld
    %lld
    ", max(ans1.second, ans2.second), max(ans1.first, ans2.first));
      return 0;
    }
    

    类型3

    题目链接:区间max

    Description

    给定一个序列a,求(max { min(a[l],a[l+1],a[l+2]…a[r]) * (r-l+1) })
    数据范围(1le nle 5 imes 10^5)

    Solution

    在暴力扫的过程中记录当前区间最小值。
    复杂度:(O(n^2)),期望得分:(80)分。
    考虑分治进行该过程,假设我们想要知道([l,r])的答案,可以把它拆分成([l,mid])([mid+1,r])和两者合并的答案。
    所以可以在分治后随便合并一下,即可通过此题。
    复杂度:(O(nlogn)),期望得分:(100)分。
    我们转换思路,考虑(a_i)成为(min)时,左右最远能拓展到的距离。
    显然,这可以用两个单调栈解决。
    复杂度:(O(n)),期望得分:(100)分。
    话说这数据造不了太大,该怎么卡(O(nlogn))啊。

    Code1(分治)

    const int N = 500005;
    ll a[N];
    int n;
    
    ll dfs(int l, int r) {
      if (l == r) return a[l];
      int mid = (l + r) >> 1;
      ll ans = max(dfs(l, mid), dfs(mid + 1, r));
      ll x = mid, y = mid + 1, h = min(a[x], a[y]);
      ans = max(ans, h << 1);
      while (x > l && y < r) {
        if (a[x - 1] < a[y + 1]) h = min(h, a[++y]);
        else h = min(h, a[--x]);
        ans = max(ans, (ll)(y - x + 1) * h);
      }
      while (x > l) {
        h = min(h, a[--x]);
        ans = max(ans, (ll)(y - x + 1) * h);
      }
      while (y < r) {
        h = min(h, a[++y]);
        ans = max(ans, (ll)(y - x + 1) * h);
      }
      return ans;
    }
    
    int main() {
      read(n);
      rep(i, 1, n) scanf("%lld", &a[i]);
      print(dfs(1, n), '
    ');
      return 0;
    }
    

    Code2(单调栈)

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 500005;
    int a[N], L[N], R[N];
    int st[N], tp = 0;
    int n;
    
    int main() {
      scanf("%d", &n);
      for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
      }
      for (int i = 1; i <= n; i++) {
        while (tp > 0 && a[st[tp]] >= a[i]) tp--;
        L[i] = st[tp] + 1;
        st[++tp] = i;
      }
      tp = 0; st[tp] = n + 1; 
      for (int i = n; i >= 1; i--) {
        while (tp > 0 && a[st[tp]] >= a[i]) tp--;
        R[i] = st[tp] - 1;
        st[++tp] = i;
      }
      
      long long res = 0;
      for (int i = 1; i <= n; i++) {
        res = max(res, (long long)a[i] * (R[i] - L[i] + 1));
      }
      printf("%lld
    ", res);
      return 0;
    }
    

    单调栈优化dp

    题目链接:JSOI2011 柠檬

    Description

    将一个数列分成若干段,从每一段中选定一个数(s_0),假设这个数在此段有(t)个,那么这一段价值为(s_0t^2),数列的总价值为每一段的价值和。
    你需要最大化总价值。
    数据范围(1le nle 100000,1le s_ile 10000)

  • 相关阅读:
    Oracle PL/SQL攻略
    Android数据库中查找一条数据 query方法详解
    验证视图MAC失败 Validation of ViewState MAC Failed
    长方体类
    用类的友元函数完成运算符的重载
    全国软件2. 三人年龄
    Android中实现带声音提示的Toast (自定义扩展Toast)
    js判断生效时间不得大于失效时间
    OpenGL运用辅助库创建规则几何对象
    Ubuntu13.04安装CUDA5.0
  • 原文地址:https://www.cnblogs.com/wlzhouzhuan/p/12579100.html
Copyright © 2011-2022 走看看