洛谷題單指南-常見最佳化技巧-P1950 長方形

五月江城發表於2024-08-30

原題連結:https://www.luogu.com.cn/problem/P1950

題意解讀:在一張n*m個格子的紙上,從沒有畫過的格子中剪出長方形的方案數。

解題思路:

1、暴力做法

列舉所有的子矩陣O(n^4),然後用二維字首和計運算元矩陣的和,透過和來判斷子矩陣是否全部是'.'。

2、最佳化做法

針對每一行進行處理,計算包含每一行每一個格子的長方形數量。

考慮每一個'.'的格子的懸線(懸線的應用,可以參考:https://www.cnblogs.com/jcwy/p/18361265

設某一行每一列j的懸線高度h[j],每一列左邊第一個小於等於h[j]的位置是l[j],右邊第一個小於h[j]的位置是r[j]

如下圖,

洛谷題單指南-常見最佳化技巧-P1950 長方形

假設已處理到第3行,第3行每列點的懸線覆蓋的是綠色區域,所以只用考慮綠色區域一共組成多少長方形。

如何計算綠色區域長方形的數量?

先考慮第1列:

洛谷題單指南-常見最佳化技巧-P1950 長方形

h[1] = 1,l[1] = 0, r[1] = 3

解讀:第一列懸線高度是1,左邊第一個小於等於h[1]的位置是0,右邊第一個小於h[1]的位置是4

所以:覆蓋第一列的所有長方形數量為(1 - l[1]) * (r[1] - 1) * h[1] = 3,也就是第一列、第一二列、第一二三列三種情況。

再考慮第2列:

洛谷題單指南-常見最佳化技巧-P1950 長方形

h[2] = 2, l[2] = 1, r[2] = 3

解讀:第二列懸線高度是2,左邊第一個小於等於h[2]的位置是1,右邊第一個小於h[2]的位置是3

所以:覆蓋第二列的所有長方形數量為(2 - l[2]) * (r[2] - 2) * h[2] = 2,也就是第二列、第二列+上一行的第二列兩種情況。

再考慮第3列:

洛谷題單指南-常見最佳化技巧-P1950 長方形

h[3] = 1, l[3] = 1, r[3] = 4

解讀:第三列懸線高度是1,左邊第一個小於等於h[3]的位置是1,右邊第一個小於h[3]的位置是4

所以:覆蓋第三列的所有長方形數量為(3 - l[3]) * (r[3] - 3) * h[3] = 2,也就是第三列、第三二列兩種情況。

以上分析,就是包含第3行所有列的長方形的數量,不重不漏。

下面問題就在於如何計算l[],r[]

要計算j左邊第一個小於等於h[j]的位置l[j],以及j右邊第一個小於h[j]的位置r[j],可以藉助單調棧。

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int n, m;
char a[N][N];
int h[N]; //h[j]表示某行第j列點的懸線高度
int l[N]; //l[j]表示某行第j列點左邊第一個懸線高度小於等於h[j]的位置
int r[N]; //r[j]表示某行第j列點右邊第一個懸線高度小於h[j]的位置
int stk[N], top;
long long ans;

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            if(a[i][j] == '.') h[j] = h[j] + 1; //計算(i,j)的懸線高度
            else h[j] = 0;
        }

        //透過單調棧計算第i行每個點j左邊第一個小於等於h[j]的位置,沒有的話就是0
        top = 0;
        for(int j = 1; j <= m; j++)
        {
            while(top && h[stk[top]] > h[j]) top--;

            l[j] = stk[top];
            stk[++top] = j;
        }

        //透過單調棧計算第i行每個點j右邊第一個小於h[j]的位置,沒有的話就是m+1
        top = 0;
        for(int j = m; j >= 1; j--)
        {
            while(top && h[stk[top]] >= h[j]) top--;

            if(top) r[j] = stk[top];
            else r[j] = m + 1;
            stk[++top] = j;
        }

        //更新答案
        for(int j = 1; j <= m; j++)
            ans += (j - l[j]) * (r[j] - j) * h[j];
    }

    cout << ans;

    return 0;
}

相關文章