P1950 长方形_NOI导刊2009提高(2)
知识点:单调栈,枚举
原题面
题目要求:
给定 一由 黑 / 白 方格构成的 \(n\times m\) 的矩形
求其中 全白子矩形 的数量
分析题意:
-
发现数据范围 较小 , \(n,m\le 1000\)
且对于一矩形, 如果两边确定, 则矩形即可确定
则可以暴力枚举 矩形的一条边 , 并进行下一步操作: -
若暴力枚举矩形的底边 ,
如何求得 以此边为底边的 合法矩形的个数?显然, 以此边为底边 能构成的 最大矩形的高度,
为 底边所有位置中 , 向上延伸的 最短的 可选位置数举个例子, 对于下列矩形:
\(00100\) 若以最底部的边为矩形底边 ,
\(01110\) 则 可构成的 最大矩形的高度为
\(11111\) 向上延伸的 最短的 可选位置数 , 即 \(2\)
\(11111\)则以此为 底边 可构成的 合法子矩形数为 \(2\) ( \(h\) 为 \(2\) )
-
则可以对于底边每一个元素 ,
再暴力枚举 它的高度 , 再暴力计算
复杂度 \(O(n^3)\) , 必然会被卡掉
考虑再进行优化 -
假设固定 最小的 高度,
若已知向左向右 可以延伸的 最长长度,
显然 也可以通过乘法原理 , 计算出合法的 子矩形的个数举个例子, 对于下列矩形:
\(10000\) 若以中间的 一列为最小高度,
\(00011\) 则 可向左延伸 \(1\) , 向右延伸 \(2\)
\(01111\) 则左侧可选位置数为1, 右侧可选位置数为2
\(11111\)\(12345\)
则合法子矩形个数为:
计算式 (3 -2 ) *(5 -3) *2 坐标位置 中间 左侧 右侧 中间 高度 -
对于上述 要求, 可以使用单调栈维护
枚举每一行元素 , 然后将每一行的每个位置 最大高度扫两边
使用单调栈 求得向左 , 向右 可以延伸的 最长长度
则可以计算出 合法子矩形的个数复杂度 \(O(nm)\) , 足以通过本题
#include<cstdio>
#include<stack>
#include<ctype.h>
#define int long long
const int MARX = 1010;
//=============================================================
int n,m,ans , up[MARX][MARX],l[MARX],r[MARX];
bool map[MARX][MARX];
char s[MARX];
//=============================================================
inline int read()
{
int s=1, w=0; char ch=getchar();
for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
return s*w;
}
void prepare()//预处理
{
n = read(),m = read();
for(int i=1; i<=n; i++)
{
scanf("%s",s+1);
for(int j=1; j<=m; j++)
map[i][j] = (s[j] == '.');
}
for(int i=1; i<=n; i++)//预处理 每一个位置 向上连续的 可选位置数
{
up[i][0] = up[i][m] = -2e9+7; //边界
for(int j=1; j<=m; j++)//向下递推
if(map[i][j]) up[i][j] = up[i-1][j] + 1;
else up[i][j] = 0;
}
}
void solve(int x)//求解 第x行的矩形数量
{
std::stack <int> st1,st2;
st1.push(1) , st2.push(m);//单调栈 初始化
for(int i=2,j=m-1; i<=m+1; i++,j--)//维护 每一个位置 向左/右最近的 高度比它低的位置
{
while(!st1.empty() && up[x][i] < up[x][st1.top()])//维护 右侧第一个比他低的位置
{
r[st1.top()] = i;
st1.pop();
}
while(!st2.empty() && up[x][j] <= up[x][st2.top()])//维护 左侧第一个比他低的位置
{
l[st2.top()] = j;
st2.pop();
}
st1.push(i), st2.push(j);//新元素入栈
}
for(int i=1; i<=m; i++) //枚举子矩形, 并计算答案
ans += (i-l[i])*(r[i]-i) * up[x][i];//乘法原理 计算矩形个数
}
//=============================================================
signed main()
{
prepare();
for(int i=1; i<=n; i++) solve(i);
printf("%lld",ans);
}