枚举(最大子矩阵,LA 3029)

感觉枚举的问题想要优化就一定要一边枚举一边维护些东西,或者要预处理之类的。


记得以前做了一道题  http://blog.csdn.net/xl2015190026/article/details/52551682 

唯一的不同是求周长最长。做的时候参考了这道题,也是枚举右下角,然后用单调栈维护左上角。在那题中随着j的增大,并不会影响单调栈内元素的单调性。然而在这题里,随着j的增大,越后面的元素增长得越快,因此后面的小的元素是有可能超过前面大的元素的。在这题中已经不能说是单调栈了,更像是一个双重有序的线性表。每次找最大值得遍历一遍。时间复杂度O(mnk)。k是栈内元素的平均数量。跑起来速度还可以。


大白书上的方法更优,只用O(mn)。思路都大同小异,就是枚举格子,维护一些信息,然后O(1)计算出答案。我要O(k)才行。大致区别就是不一定要维护左上角,因为左上角的单调性已经无法保证了,所以不能避免再次枚举。应该换一下思路。之所以无法保证O(1)计算,那主要是因为维护的信息量不够。要加多一点。或许再维护下右上角= =?但两边的高度需要统一,那么以谁为标准呢?该取哪那个左上角或右上角呢?信息量似乎还是不够,还是需要枚举。那就再加一个信息吧,我们需要统一高度,那不妨取当前格的最大高度好了。然后尽量的往两边延伸。维护好这个就能O(1)算出了。

如何维护是个大问题,高度很好维护,加一就好了。其实左右也很好维护的。如果上一层能延伸到这么远,那阻碍这一层的唯一因素就是这一层的格子了。遍历一下就能计算出来了。特别的为了方便维护,如果遇到石头,那就要初始化,即left[i]=0,right[i]=N,up[i]=0。以便下一层的维护。

这个方法只会快一点。估计是栈内元素很少。


查出了一些逻辑上的错误,但侥幸的不影响结果。


双重有序

#include<bits/stdc++.h>
#define maxn 1010
using namespace std;

int M,N;
char MAP[maxn][maxn];
int MAX[maxn][maxn];

inline void get(char& a)
{
    do
    {
        a=getchar();
    }while(a!='F'&&a!='R');
}

struct Node
{
    int c,h;
}NODE[maxn];
int top;

void add(int c,int h)
{
    if(!top||NODE[top-1].h<h)
    {
        NODE[top].c=c;
        NODE[top++].h=h;
    }
    else
    {
        int k=top-1;
        while(k-1>=0&&NODE[k-1].h>=h) k--;
        top=k+1;
        NODE[k].h=min(NODE[k].h,h);
    }
}

int main()
{
    int K;
    scanf("%d",&K);
    while(K--)
    {
        scanf("%d %d",&M,&N);
        for(int i=1;i<=M;i++)
            for(int j=1;j<=N;j++)
                get(MAP[i][j]);
        for(int i=1;i<=N;i++)
        {
            int u,d;
            u=d=1;
            while(u<=M)
            {
                while(u<=M&&MAP[u][i]=='R') u++;
                d=u;
                while(d<=M&&MAP[d][i]=='F')
                {
                    MAX[d][i]=u;
                    d++;
                }
                u=d;
            }
        }
        int ans=0;
        for(int i=1;i<=M;i++)
        {
            top=0;
            for(int j=1;j<=N;j++)
            {
                if(MAP[i][j]=='R')
                {
                    top=0;
                    continue;
                }
                add(j,i-MAX[i][j]+1);
                for(int i=0;i<top;i++)
                    ans=max(ans,(j-NODE[i].c+1)*NODE[i].h);
            }
        }
        printf("%d\n",ans*3);
    }
    return 0;
}

维护高度,左右延伸

#include<bits/stdc++.h>
#define maxn 1010
using namespace std;

int M,N;
char MAP[maxn][maxn];
int up[maxn],lft[maxn],rit[maxn];

void get(char& a)
{
    do
    {
        a=getchar();
    }while(a!='F'&&a!='R');
}

int main()
{
    int K;
    scanf("%d",&K);
    while(K--)
    {
        scanf("%d %d",&M,&N);
        for(int i=1;i<=M;i++)
            for(int j=1;j<=N;j++)
                get(MAP[i][j]);
        for(int i=1;i<=N;i++)
        {
            up[i]=lft[i]=0;
            rit[i]=N;
        }
        int ans=0;
        for(int i=1;i<=M;i++)
        {
            int lo=N+1;
            for(int j=N;j>=1;j--)
                if(MAP[i][j]=='F')
                {
                    rit[j]=min(rit[j],lo-1);
                }
                else
                {
                    lo=j;
                    rit[j]=N;
                }
            lo=0;
            for(int j=1;j<=N;j++)
            {
                if(MAP[i][j]=='F')
                {
                    lft[j]=max(lft[j],lo+1);
                    up[j]++;
                }
                else
                {
                    lo=j;
                    lft[j]=0;
                    up[j]=0;
                }
                if(MAP[i][j]=='F')
                {
                    ans=max(ans,(rit[j]-lft[j]+1)*up[j]);
                }
            }
        }
        printf("%d\n",ans*3);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值