LGP1950 长方形 题解
单调栈。
每一行为矩形底边所在直线可以分成n个独立的子问题单独考虑。
首先定义(i,j)的高度um[i][j]表示从a[i][j]开始往上最长的.
连续段长度
想到那道“直方图求最大矩形”的题目,我们可以处理出每一列的左侧第一个小于等于um[它]的下标l[j],和右侧第一个小于它的——r[j]。
上面粗体需要细心理解。这样可以做到有效地将方案划分成互相独立的部分。
针对每一个j,将(j-l[j])·(r[j]-j)·um[i][j]累加到答案中,把每一行的答案加起来就是最终答案。
为什么我们一定不会重复统计一个矩形呢?
反证法:假设我们的j=x,在[l[j],r[j]]中存在一个y跟x统计了同一个矩形,不妨设y>x。显然x,y的高度必然有大于、等于、小于三种关系
(1)um[i][x]<um[i][y],则y统计的矩形的左边界必然达不到x,假设不成立
(2)um[i][x]=um[i][y],则y统计的矩形的左边界必然达不到x,假设不成立
(3)um[i][x]>um[i][y],则x统计的矩形的右边界必然达不到y,假设不成立
综上:每个矩形被且仅被统计1次
本题解法的精髓就在于单调栈的一侧取等一侧不取等的应用,有效避免了重复统计。
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int n,m,top,s[N],p[N],l[N],r[N],um[N][N];
char a[N][N];
int main()
{
scanf("%d%d",&n,&m);
long long ans=0;
for(int i=1;i<=n;i++){
scanf("%s",a[i]+1);
top=0;p[0]=0;
for(int j=1;j<=m;j++){
if(a[i][j]=='.')um[i][j]=um[i-1][j]+1;
while(top&&s[top]>=um[i][j])top--;
l[j]=p[top];
s[++top]=um[i][j],p[top]=j;
}
top=0;p[0]=m+1;
for(int j=m;j>=1;j--){
while(top&&s[top]>um[i][j])top--;
r[j]=p[top];
s[++top]=um[i][j],p[top]=j;
}
for(int j=1;j<=m;j++)
ans+=(j-l[j])*(r[j]-j)*um[i][j];
}
cout<<ans<<endl;
}
[变式]给你 \(n\times m(n,m\le 6000)\) 的01矩阵中为 1 的位置的坐标 \((x_i,y_i)(1\le i\le k,k\le n\times m)\),给出时是乱序的,你一排序就TLE,请问你还会求全1矩阵的数量吗?
[answer]可以 O(k) 解决。本质上我们只要知道每个为1元素的h(上方延伸高度)、le、ri即可,而h可以通过同一列中的“为1”行连续段求出,le,ri也可以类似地找到一个行内连通块的左端点然后向右遍历。这样就可以不枚举到0。
#include <bits/stdc++.h>
using namespace std;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=1005;
int n,m,tp,n_p,tot,a[N][N],xx[N*N],yy[N*N],stk[N*N],le[N*N],ri[N*N],phi[N*N],p[N*N],h[N][N],q[N*N][2];
long long ans=0;
bool v[N*N];
int o[N][N];
int main(){
cin>>n>>m;phi[1]=1;
for(int i=2;i<=1e6;i++){
if(!v[i])p[++n_p]=i,phi[i]=i-1;
for(int j=1;j<=n_p&&p[j]*i<=1e6;j++){
v[i*p[j]]=1;
if(i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];
break;
}
else phi[i*p[j]]=phi[i]*phi[p[j]];
}
}
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)a[i][j]=read(),xx[a[i][j]]=i,yy[a[i][j]]=j;
for(int d=1;d<=n*m;d++){
tot=0;
for(int i=d;i<=n*m;i+=d)o[xx[i]][yy[i]]=++tot,q[tot][0]=xx[i],q[tot][1]=yy[i];
for(int i=1;i<=tot;i++){
if(!(q[i][0]==1||!o[q[i][0]-1][q[i][1]]))continue;
int oo=q[i][0],hh=0;
while(oo<=n&&o[oo][q[i][1]])h[oo][q[i][1]]=++hh,oo++;
}
for(int i=1;i<=tot;i++){
if(!(q[i][1]==1||!o[q[i][0]][q[i][1]-1]))continue;
int c=q[i][1];
tp=0;stk[0]=q[i][1]-1;
int r=q[i][0];
while(c<=m&&o[r][c]){
while(tp&&h[r][stk[tp]]>h[r][c])tp--;
le[o[r][c]]=stk[tp]+1;
stk[++tp]=c;
c++;
}
}
for(int i=1;i<=tot;i++){
if(!(q[i][1]==m||!o[q[i][0]][q[i][1]+1]))continue;
int c=q[i][1];
tp=0;stk[0]=q[i][1]+1;
int r=q[i][0];
while(c>=1&&o[r][c]){
while(tp&&h[r][stk[tp]]>=h[r][c])tp--;
ri[o[r][c]]=stk[tp]-1;
stk[++tp]=c;
c--;
}
}
long long cnt=0;
for(int i=1;i<=tot;i++)
cnt+=(q[i][1]-le[i]+1ll)*(ri[i]-q[i][1]+1ll)*h[q[i][0]][q[i][1]],
o[q[i][0]][q[i][1]]=0;
ans+=cnt*phi[d];
}
cout<<ans;
}//核心部分就是这个问题