题意:
给一个\(n×m\)矩阵,问有多少子矩阵,对于其中任意一个数,都满足它小于它的上下左右四个方向中第一个在矩阵外面的数。
\(1\leq n,m\leq 2500\)。
写一个\(O(nmlognm)\)的做法。
首先,对于每一行,每一列,分别求出其中的合法区间。
显然,只要区间中的最大值满足条件(即它小于区间外侧的两个数)即可。
因此,我们可以枚举最大值,用单调栈求出两侧不大于它的区间\((l,r)\),那么,只有\((l-1,r+1)\)可能满足条件。
这样,每个最大值只会贡献一个区间,因此总的区间数是\(O(nm)\)的。
求出这部分后,只要子矩阵的每行每列都能对应一个合法区间,它就是合法的了。
再求出每个子区间向下(向右)能延伸的长度,这个可以边扫描,边更新。
求答案时,我们可以枚举这个子矩阵最上边的那行,并枚举那行中的合法区间,设它的向下延伸长度为\(x\),区间长度为\(y\)。
之后,再枚举它的左端点的列中,上端点位于这行的一个区间。
那么,只要这个区间长度不大于\(x\),向右延伸长度不小于\(y\),就会产生一个答案。
可以发现,这是一个类似二维数点的问题,直接扫描线+树状数组维护即可。
注意细节
代码:
#include
#include
using namespace std;
struct SJd
{
int l,r;
SJd(){}
SJd(int L,int R)
{
l=L;r=R;
}
};
int getlr(int sz[2502],int n,SJd jg[2502])
{
int l[2502],r[2502],m=0;
for(int i=0;i0&&sz[l[i]-1]<=sz[i])
l[i]=l[l[i]-1];
}
for(int i=n-1;i>=0;i--)
{
while(r[i]+10&&r[i]+1sz[i])
jg[m++]=SJd(l[i],r[i]);
}
return m;
}
SJd ha[2502][2502],li[2502][2502];
int sh[2502],sl[2502],sz[2502][2502];
int dn[2502][2502],ri[2502][2502],dp[2502][2502];
bool bk[2502][2502];
struct SPx
{
int j,ri;
SPx(){}
SPx(int J,int RI)
{
j=J;ri=RI;
}
};
vector ve[2502][2502];
int C[2502][2502],N;
void add(int a,int i)
{
i+=1;
for(int j=i;j>0;j-=j&(-j))
C[a][j]+=1;
}
void clean(int a,int i)
{
i+=1;
for(int j=i;j>0;j-=j&(-j))
C[a][j]=0;
}
int sum(int a,int i)
{
int jg=0;i+=1;
for(int j=i;j<=N;j+=j&(-j))
jg+=C[a][j];
return jg;
}
vector vv[2510];
int main()
{
int n,m;
scanf("%d%d",&n,&m);N=m;
for(int i=0;i=0;i--)
{
for(int j=0;j=0;i--)
{
for(int j=0;j