题
给定一个 n×m n × m 的01矩阵,求全是1的子矩阵个数。
其中 n,m≤5000 n , m ≤ 5000 。
解
暴力
先来说说暴力思路,最暴力就是四方枚举子矩阵,平方check,总复杂度 O(n6) O ( n 6 ) 不满。考虑有什么地方可以优化?
我们想到子矩阵之间是有包含关系的,如果找到一个全是1的大子矩阵,那么这个子矩阵的子矩阵肯定也都符合条件,就不用枚举了。
- 顺便一提,这是个经典问题,有别的更经典的变式(例如求全是1的最大子矩阵),不过思路都一样。
更优秀的暴力
根据上面的思想,我们这样来优化:
假设我们遍历矩阵的时候找到一个1,我们相当于已经发现一个 1×1 1 × 1 的子矩阵,可以计入答案。然后我们肯定是要找这个1旁边还有没有其它1与它相邻(上下左右),来构成一个 1×2 1 × 2 的子矩阵,于是我们往四个方向拓展——但是,我们知道这个点左边的点按正常顺序一定会先被遍历到,然后左边的点也会拓展然后将这个点计入,这样就重复统计了,上面的点同理。因此我们考虑就从往右边和下边去拓展,碰到0就停下,这样就不会记重。按这样的思想做可能能到 O(n4) O ( n 4 ) 不满,不过我没去试。
我们发现往右拓展完全可以预处理,然后再优化:
如果上面的优化解释你听的不太清楚,没关系,看下面就行。
更更优秀的暴力
来张样例:
1 1 1 1 0
1 1 0
1 1 1 0
1 0
0
从左上的点开始,拓展成这样。我们发现上下是有影响的。我们容易考虑一个上下边界分别为第 i i 行、第
行的矩阵,则这个矩阵的右边界就是第 i i 到第
行中右端最左的。如例子中跨越 1~3 行矩阵的右边界就是2了。
于是我们先预处理向右拓展的部分,为了方便处理我们也可以处理成向左拓展(反正只要定向了就不会重),记 fi,j f i , j 表示从 (i,j) ( i , j ) 这个点一直往左,直到第一个0,经过了多少个为1的点。如下面这个矩阵:
1 1 0
0 1 0
1 1 1
它的 f f 数组记成:
1 2 0
0 1 0
1 2 3
然后我们枚举每一个位置,对于每个位置
我们计算的是:这个位置在该子矩阵的最右侧,且它的横向长度