[IOI2019]矩形区域

题意:
给一个\(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

你可能感兴趣的:([IOI2019]矩形区域)