【题目】:
小明今天突发奇想,想从一张用过的纸中剪出一个长方形。
为了简化问题,小明做出如下规定:
(1)这张纸的长宽分别为 n , m n,m n,m。小明讲这张纸看成是由 n × m n \times m n×m个格子组成,在剪的时候,只能沿着格子的边缘剪。
(2)这张纸有些地方小明以前在上面画过,剪出来的长方形不能含有以前画过的地方。
(3)剪出来的长方形的大小没有限制。
小明看着这张纸,想了好多种剪的方法,可是到底有几种呢?小明数不过来,你能帮帮他吗?
【思路】:
记未填充的点为好点,已填充的点为坏点。
记 u p [ i ] up[i] up[i]为第 i i i列可以往上延伸多少
分别算出 l [ i ] l[i] l[i]和 r [ i ] r[i] r[i],分别表示 u p up up中左边第一个不大于 u p up up的数和右边第一个小于 u p up up的数(可以用单调栈写)
答案即为 ∑ i = 1 n u p [ i ] × ( i − l [ i ] ) × ( r [ i ] − i ) \sum ^n _{i=1} up[i] \times (i-l[i]) \times (r[i]-i) ∑i=1nup[i]×(i−l[i])×(r[i]−i)
【代码】:
//By HPXXZYY
#include
#include
#include
#include
using namespace std;
#define ll long long
const int N=1020;
ll l[N],r[N],up[N];
ll k[N],n,m,top,i,j;
int d[N][N];ll ans;
inline void calc_l(){
top=0;int i;
for(i=m;i;i--){
while (top&&up[i]<=up[k[top]]){
l[k[top]]=i;top--;
}
k[++top]=i;
}
while (top){
l[k[top]]=0;
--top;
}
}//利用单调栈求l数组
inline void calc_r(){
top=0;int i=0;
for(i=1;i<=m;i++){
while (top&&up[i]<up[k[top]]){
r[k[top]]=i;top--;
}
k[++top]=i;
}
while (top){
r[k[top]]=m+1;
--top;
}
}//利用单调栈求r数组
inline ll calc_answer(){
calc_l();calc_r();
register ll ans=0;
for(int i=1;i<=m;i++)
ans+=up[i]*(i-l[i])*(r[i]-i);
return ans;
}//统计答案
int main(){
cin>>n>>m;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++){
char ch;cin>>ch;
if (ch=='*') d[i][j]=1;
}
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
up[j]++;if (d[i][j]) up[j]=0;
}
ans+=calc_answer();
}
cout<<ans;
return 0;
}