【论题选编】单调栈和全 1 子矩阵

(低技术力预警)

有时我们希望对 01 矩阵中,每个全 1 子矩阵做一些统计。

一种通用的方法是单调栈。我们从上到下枚举行,然后考察以该行为底边的所有全 1 子矩阵。利用单调栈,我们可以得到以该行为底边的所有极大子矩阵

假设对一个高为 h h h、宽为 w w w 的矩形,其权值为 f ( h , w ) f(h, w) f(h,w),那么对一个高为 h h h、宽为 w w w 的极大子矩阵而言,其包含的所有矩形的贡献和为
s u m ( h , w ) = ∑ h ′ = 1 h ∑ w ′ = 1 w ( w − w ′ + 1 ) f ( h ′ , w ′ ) sum(h, w) = \sum_{h'=1}^{h}\sum_{w' = 1}^{w} (w-w'+1)f(h', w') sum(h,w)=h=1hw=1w(ww+1)f(h,w)

g ( h , w ′ ) = ∑ h ′ = 1 h f ( h ′ , w ′ ) g(h, w') = \sum_{h'=1}^{h}f(h', w') g(h,w)=h=1hf(h,w),有
s u m ( h , w ) = ∑ w ′ = 1 w ( w − w ′ + 1 ) g ( h , w ′ ) = w ∑ w ′ = 1 w g ( h , w ′ ) − ∑ w ′ = 1 w ( w ′ − 1 ) g ( h , w ′ ) sum(h, w) = \sum_{w' = 1}^{w} (w-w'+1)g(h, w') = w \sum_{w' = 1}^{w} g(h, w') - \sum_{w' = 1}^{w} (w'-1)g(h, w') sum(h,w)=w=1w(ww+1)g(h,w)=ww=1wg(h,w)w=1w(w1)g(h,w)

如果我们能快速计算出 f f f g g g,就能通过预处理前缀和,从而在 O ( 1 ) O(1) O(1) 内计算出 s u m ( h , w ) sum(h, w) sum(h,w)

然而我们不能简单的把所有极大子矩阵的贡献全部加起来,因为可能会算重。例如:

    1 1
    1 1
  1 1 1
  1 1 1
1 1 1 1
1 1 1 1

在单调栈出栈的时候,三个极大子矩阵相互有重合的部分。因此还需要在出栈时通过容斥去掉这些算重的部分。

具体怎么容斥,看代码比较方便:

#define REP(temp, init_val, end_val) for (int temp = init_val; temp <= end_val; ++temp)
#define REPR(temp, init_val, end_val) for (int temp = init_val; temp >= end_val; --temp)

stack<pair<int, int> > st;
// height[j] 是第 j 列的高
height[n + 1] = 0;
int ans = 0;
REP(i, 1, n){
    
    
    REP(j, 1, n){
    
    
        if (s[i][j] == '0') height[j] = 0;
        else ++height[j];
    }
    while (!st.empty()) st.pop();
    REP(j, 1, n + 1){
    
    
        int curw = 0;
        while (!st.empty() && st.top().first > height[j]){
    
    
            if (curw > 0)
                ans -= sum[st.top().first][curw];
            curw += st.top().second;
            ans += sum[st.top().first][curw];
            st.pop();
        }
        if (curw > 0)
            ans -= sum[height[j]][curw];
        if (!st.empty() && st.top().first == height[j]){
    
    
            st.top().second += curw + 1;
        } else if (height[j] > 0){
    
    
            st.emplace(height[j], curw + 1);
        }
    }
}

例 1

题意:给定一个 01 矩阵,问有多少个全 1 子矩阵满足一条边的边长是另一条边边长的倍数。(来源:2020 计蒜之道线上决赛 E)

f ( h , w ′ ) = ∑ h ′ = 1 h [ h ′ ∣ w ′ ∨ w ′ ∣ h ′ ] f(h, w') = \sum_{h' = 1}^{h} [h' | w' \vee w' | h'] f(h,w)=h=1h[hwwh] 即可套用上述框架。

例 2

题意:给出一个 R × C R\times C R×C 的网格图,其中有些格子被涂成黑色。多次询问,给定宽 w w w 和高 h h h,问网格图中有多少个位置可以放置 w × h w \times h w×h 的矩形(不可旋转),满足矩形内没有黑色的格子,且边与网格线重合。(来源:SWERC 2017 B 题)

询问 w × h w\times h w×h 时,令 f ( x , y ) = [ x ≥ h ∧ y ≥ w ] f(x, y) = [x \ge h \wedge y \ge w] f(x,y)=[xhyw] 即可套用上述框架。

由于有很多组询问,因此需要预处理每一组 ( w , h ) (w, h) (w,h) 的答案,故在累积答案时需要用到前缀和的技巧。具体细节可以参考下面的代码。

#include <bits/stdc++.h>
using namespace std;
stack<pair<int, int> > st;
int X, Y, N, D;
int block[2005][2005] = {
    
    0}, hh[2005] = {
    
    0};
int ans[2005][2005] = {
    
    0};
int main(){
    
    
    scanf("%d%d%d%d", &X, &Y, &N, &D);
    // 处理出黑色区域
    for (int i = 1, xx1, xx2, yy1, yy2; i <= N; ++i){
    
    
        scanf("%d%d%d%d", &xx1, &xx2, &yy1, &yy2);
        ++block[xx1 + 1][yy1 + 1];
        --block[xx1 + 1][yy2 + 1];
        --block[xx2 + 1][yy1 + 1];
        ++block[xx2 + 1][yy2 + 1];
    }
    for (int i = 1; i <= X; ++i)
        for (int j = 1; j <= Y; ++j)
            block[i][j] += block[i][j - 1];
    for (int i = 1; i <= X; ++i)
        for (int j = 1; j <= Y; ++j)
            block[i][j] += block[i - 1][j];
    
    // 应用单调栈计算答案
    hh[X + 1] = 0;
    for (int j = Y; j >= 1; --j){
    
    
        for (int i = 1; i <= X; ++i){
    
    
            if (block[i][j]) hh[i] = 0;
            else ++hh[i];
        }
        while (!st.empty()) st.pop();
        for (int i = 1; i <= X + 1; ++i){
    
    
            int curw = 0;
            while (!st.empty() && st.top().first > hh[i]){
    
    
                if (curw > 0)
                    ans[st.top().first][curw] -= 1;
                curw += st.top().second;
                ans[st.top().first][curw] += 1;
                st.pop();
            }
            if (curw > 0)
                ans[hh[i]][curw] -= 1;
            if (!st.empty() && st.top().first == hh[i]){
    
    
                st.top().second += curw + 1;
            } else if (hh[i] > 0){
    
    
                st.emplace(hh[i], curw + 1);
            }
        }
    }

    // 做两次后缀和,再对 h 这一维做一次后缀和,方便回答
    for (int i = 1; i <= Y; ++i)
        for (int j = X - 1; j >= 1; --j)
            ans[i][j] += ans[i][j + 1];
    for (int i = 1; i <= Y; ++i)
        for (int j = X - 1; j >= 1; --j)
            ans[i][j] += ans[i][j + 1];
    for (int j = 1; j <= X; ++j)
        for (int i = Y - 1; i >= 1; --i)
            ans[i][j] += ans[i + 1][j];
    
    // 回答询问   
    for (int i = 1, qw, qh; i <= D; ++i){
    
    
        scanf("%d%d", &qw, &qh);
        printf("%d\n", ans[qh][qw]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zqy1018/article/details/109269124