题目传送门
首先我们了解一下什么是单调栈(不过神犇们都应该会了)
1.定义
单调栈是一种可以以 O(n) 的时间复杂度解决类似求数组中某个位置i往左(或往右)第一个比i这个位置大(或小)的数(或位置)的问题的算法。
2.怎么做?
首先定义一个栈(先进后出)栈中存的是目前还没有答案的数,易得栈中元素递减(不然与栈的定义不符),对于每一次要将元素压入栈,先将栈顶所有大于它的元素记录答案(就是要压入的元素)并弹出,因为我们要求左边第一个,所以以从右向左的顺序入栈(因为是先入栈再记录答案,所以要从元素从答案入栈)
//代码,单调栈
void ddzl(){
top=0;//清空栈
for(int i=m;i>=1;i--){
while(top!=0&&h[i]<=h[k[top]]) l[k[top]]=i,top--;//记录答案
top++;
k[top]=i;//入栈
}
while(top) l[k[top]]=0,top--;//对没有答案做特殊处理
}
为什么要用这个玩意(单调栈)?
先看个例子
n*m个格子中有多少个矩形?
设矩形的一竖边为x,横边为y;
对于同一行来说横边的长度可以为 1 , 2 , 3 …, m
而这些长度对应的不同位置条数为m ,m-1,m-2,…, 1
所以横边的数量为(1+m)*m/2
同理竖边的数量为(1+n)*n/2
所以共以共有n(1+m)m(1+n)/4条
那如果是这样的图案呢?
那就需要找一个完整的大矩形,再算矩形个数再相加,那这就需要用到单调栈
3.怎么算方案数?
定义 h[i] 为当前行第 i 列可向上最多能画多少
使用单调栈算出 l [ i ] 和 r [ i ],l [ i ] 是 h 中第i个位置左边第一个小于或等于h[i]的位置
r [ i ] 是 h 中第i个位置右边第一个小于h[i]的位置
为什么一个是小于等于一个是小于?
这类似于 [1,2)(前闭后开或前能取到,后取不到)(当然啦,你也可以前开后闭)
那样 [1,2),[2,3),[3,4)这样就不会重复计算。
举个例子
对每一列求出被这一列的高度限制的长方形数为
( i - l [ i ] )× ( r [ i ] - i ) × h [ i ]( i - l [ i ] )× ( r [ i ] - i )
表示某一行中的横边包括i这个位置的个数
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915132057388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NjAwMTU1MA==,size_16,color_FFFFFF,t_70#pic_center)
比如上图i的左边能选3个,右边能选2个,那么覆盖i这个点的条数为2*3条
先上代码,先上代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005;
int n,m,s[N][N],l[N],r[N],h[N],k[N],top;
ll ans;
char p;
void ddzl()
{
top=0;
for(int i=m;i>=1;i--)
{
while(top!=0&&h[i]<=h[k[top]])l[k[top]]=i,top--;
k[++top]=i;
}
while(top)l[k[top]]=0,top--;
}
void ddzr()
{
top=0;
for(int i=1;i<=m;i++)
{
while(top!=0&&h[i]<h[k[top]])r[k[top]]=i,top--;
k[++top]=i;
}
while(top)r[k[top]]=m+1,top--;
}
void solve()
{
ddzl();
ddzr();
for(int j=1;j<=m;j++)
ans+=1ll*h[j]*(j-l[j])*(r[j]-j);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>p;
if(p=='.')s[i][j]=0;
else s[i][j]=1;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
if(!s[i][j])h[j]++;
else h[j]=0;
solve();
}
cout<<ans<<endl;
return 0;
}