1. 程式人生 > 其它 >[ZJOI2007]棋盤製作題解 & 懸線法

[ZJOI2007]棋盤製作題解 & 懸線法

題意簡述

給定 \(n \times m\) 的 01矩陣,從中找到最大的正方形和矩形使得 01 交錯。

解題思路

採用懸線法。

懸線法

即對於每一行的狀態,用一根橫著的線左右移動,直到不滿足條件或者到達邊界為止,線的兩端即為符合要求的區間。

定義狀態

$ L[i][j] $ 表示從 \((i, j)\) 能到達的最左位置。
$ R[i][j] $ 表示從 \((i, j)\) 能到達的最右位置。
$ up[i][j] $ 表示 \((i, j)\) 能向上擴充套件多少層(包括自己這一層)。

初始化

假如只有自己,自己能到達的最左最右位置只有自己這兒;自己只有一層。

  • \(L[i][j] = j;\)
  • \(R[i][j] = j;\)
  • \(up[i][j] = 1;\)

狀態轉移

一行中的不同列

列舉 \(j\)

對於 \(L\),僅當第 \(j\) 位能由 \(j - 1\) 擴充套件過來:

  • \(L[i][j] = L[i][j - 1];\)

對於 \(R\),僅當第 \(j\) 位能由 \(j + 1\) 擴充套件過來:

  • \(R[i][j] = R[i][j + 1];\)

每行之間

僅當 \((i, j)\) 能與 \((i - 1, j)\) 聯通,即 \((i, j)\) 能由 \((i- 1, j)\) 擴充套件過來時:

  • $L[i][j] = max(L[i][j], L[i - 1][j]); $
  • $R[i][j] = min(R[i][j], R[i - 1][j]); $
  • \(up[i][j] = up[i - 1][j] + 1.\)

假設下圖中綠色的方塊為 $(i, j) $,那麼紅色的方塊即為 $(i - 1, j) $。

原來的 \(L[i][j]\) 較大,更新狀態之後反而變小了。因為更新之後,$L[i][j] $ 代表的是藍色方框內 $(i, j) $ 能到達的最左位置。\(R[i][j]\) 同理。

那可能有人有疑問了,要按照這樣轉移,最後是不是隻考慮了下圖藍色的部分?可是答案是綠色的部分啊?

其實,按照剛才那樣轉移,綠色框也會考慮到。

我們轉移狀態之前,保證 \((i, j)\)

能由 \((i- 1, j)\) 擴充套件過來,那麼下圖中填充為藍色的部分的 \(L,R\) 表示藍色框的寬。

而此時下圖中填充為綠色的部分的 \(L,R\),就可以表示綠色框的寬了。

仔細想一下,下圖填充為綠色部分的 \(L,R\) 沒有被上一行的 \(L,R\) 所影響,那麼他們的 \(L,R\) 表示的即為整個綠色框的寬。在遍歷 \(i,j\) 的時候,可以正常計算綠色框的大小,即綠色框被考慮到了。

程式碼

const int N = 2005;
int n, m, L[N][N], R[N][N], up[N][N];
bitset<N> b[N];

int main()
{
    n = read(), m = read();
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
        {
            b[i][j] = read();
            L[i][j] = j, R[i][j] = j;
            up[i][j] = 1;
        }
    }

    for (int i = 1; i <= n; ++i)
        for (int j = 2; j <= m; ++j)
            if (b[i][j] != b[i][j - 1])
                L[i][j] = L[i][j - 1];

    for (int i = 1; i <= n; ++i)
        for (int j = m - 1; j >= 1; --j)
            if (b[i][j] != b[i][j + 1])
                R[i][j] = R[i][j + 1];

    int ans1 = 0, ans2 = 0;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= m; ++j)
        {
            if (i > 1 and b[i][j] != b[i - 1][j])
            {
                up[i][j] = up[i - 1][j] + 1;
                L[i][j] = max(L[i][j], L[i - 1][j]);
                R[i][j] = min(R[i][j], R[i - 1][j]);
            }
            int len = R[i][j] - L[i][j] + 1;
            ans2 = max(ans2, len * up[i][j]);
            ans1 = max(ans1, min(len, up[i][j]) * min(len, up[i][j]));
        }
    }

    W(ans1, '\n'), W(ans2, '\n');
    return 0;
}