【题目】:
小明今天突发奇想,想从一张用过的纸中剪出一个长方形。
为了简化问题,小明做出如下规定:
(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 <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
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;
}