原題連結: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]
如下圖,
假設已處理到第3行,第3行每列點的懸線覆蓋的是綠色區域,所以只用考慮綠色區域一共組成多少長方形。
如何計算綠色區域長方形的數量?
先考慮第1列:
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列:
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列:
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;
}