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);
}
posted @ 2019-10-11 21:28  Luckyblock  阅读(93)  评论(0编辑  收藏  举报