最大全0/1子矩阵

我们常常会遇到这样的问题:给定一个01矩阵,求其中全0/1的最大子矩阵的面积。

简单的模板题如:[Luogu]P4147,复杂点的有[Luogu]P5300

下面我们介绍一种比较容易的算法:悬线法

其实悬线法更多的是一种思想,许多最大化子矩阵的问题也能用这种思想解决。

我们想象有一条竖线,这条线的上端要么是边界,要么是障碍点,然后我们让这条线向左右拓展,直到碰到边界和障碍点。

求出一个位置的悬线后,我们就能直接计算$S = H(R - L + 1)$。然后对所有的$S$取$max$即为答案,正确性是显而易见的(不会证)

现在的问题是:对于每个位置,我们怎么计算它悬线的位置。

考虑$O(n^3)$的暴力算法:我们枚举每个位置,然后对于这个位置,$O(n)$的向其他方向拓展。

$O(n^2)$的枚举位置已经是下界,无法再优化,于是我们考虑优化计算边界的过程。

注意到在暴力算法中,我们每到一个位置都要重新计算一遍它悬线的边界,其实这是没有必要的,我们完全可以根据它前一个位置的边界来递推出当前位置的边界。

具体地,定义$up[i][j], left[i][j], right[i][j]$分别表示位置$(i, j)$的上边界,左边界,右边界的位置。我们可以这样来进行计算:

 1 //n*m的矩阵
 2 for(int i = 1; i <= m; ++i) right[0][i] = m;
 3 for(int i = 1; i <= n; ++i)
 4 {
 5     int lmax = 1, rmin = m;
 6     for(int j = 1; j <= m; ++j)
 7     {
 8         if(G[i][j]) //当前位置是障碍点
 9         {
10             lmax = j + 1;
11             up[i][j] = left[i][j] = 0;
12         }
13         else
14         {
15             up[i][j] = up[i - 1][j] + 1; //利用上一行的信息来计算
16             left[i][j] = max(lmax, left[i - 1][j]); //还要考虑到上一行的左边界
17         }
18     }
19     for(int j = m; j; --j)s
20     {
21         if(G[i][j])
22         {
23             rmin = j - 1;
24             right[i][j] = m; //设置成m,避免影响下一行的计算(第2行和第11行的赋值也是同理)
25         }
26         else
27         {
28             right[i][j] = min(rmin, right[i - 1][j]);
29             ans = max(ans, (right[i][j] - left[i][j] + 1) * up[i][j]); //计算面积,取最大值
30         }
31     }
32 }

猜你喜欢

转载自www.cnblogs.com/Aegir/p/11105131.html