【zjoi2007】棋盘制作(悬线法+单调栈)

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个8*8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。

而我们的主人公小Q,正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定将棋盘扩大以适应他们的新规则。

小Q找到了一张由N*M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。

不过小Q还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。

于是小Q找到了即将参加全国信息学竞赛的你,你能帮助他么?

输入格式:

包含两个整数 N N M,分别表示矩形纸片的长和宽。接下来的 N N 行包含一个NM的01矩阵,表示这张矩形纸片的颜色(0表示白色,1表示黑色)。

输出格式:

包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋盘的面积(注意正方形和矩形是可以相交或者包含的)。

输入样例#1:
3 3
1 0 1
0 1 0
1 0 0
输出样例#1:
4
6

链接:棋盘制作

解:

首先我们可以把行列数加起来为偶数(或奇数,看心情)的数取反,这样题目就变成了最大子矩阵问题。即在给出的矩形内取一个面积最大的数字相同的矩形。

然后我们可以很容易的想出 n3 n 3 算法:枚举所有矩形,判断是否符合条件,取最大值。(判断可以用前缀和做到 O(1) O ( 1 ) )。做到这里,你在洛谷上可以AC了(吐槽一下洛谷的水数据)。

正解是悬线法,表示做这道题的时候并不知道这种做法。简单来说:1、对于每个点记录它向上能拓展到的最大点。这个操作可以通过递推n^2做到。2、从左到右和从右到左扫一遍,记录每个点拓展的最大线段向左向右分别扩展的最大距离。3、对于每个点计算答案取最大值。我们发现对于第二个操作,暴力只能 n3 n 3 做,这里就需要单调栈了,对于每一行我们使用单调栈维护单增,要是当前点扩展长度比栈顶大,则弹栈至合法。然后当前点坐标和栈顶点坐标距离就是能扩展的距离。

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#include<stack>  
using namespace std;  
struct lxy{  
int ip,n;  
};  
int h[2005][2005];  
int l[2005][2005];  
int r[2005][2005];  
int n,m;  
int a[2005][2005];  
int ans1=0,ans2=0;  
stack d;  

int main()  
{  
    scanf("%d%d",&n,&m);  
    for(int i=1;i<=n;i++)  
      for(int j=1;j<=m;j++)  
      {  
          scanf("%d",&a[i][j]);  
          if((i+j)%2==1)  
            a[i][j]=a[i][j]^1;  
      }  
    for(int i=1;i<=m;i++)  
      for(int j=1;j<=n;j++)  
      {  
          if(j==1||(a[j][i]!=a[j-1][i]))  
            h[j][i]=1;  
          else  
            h[j][i]=h[j-1][i]+1;  
      }  
    for(int i=1;i<=n;i++)  
    {  
        lxy p;p.ip=1;p.n=h[i][1];  
        d.push(p);  
        for(int j=2;j<=m;j++)  
        {  
            if(a[i][j]!=a[i][j-1])d.top().n=0;  
            while(!d.empty()&&h[i][j]<=d.top().n)  
              d.pop();  
            if(d.empty())l[i][j]=j-1;  
            else  
              l[i][j]=j-d.top().ip-1;  
            p.ip=j;p.n=h[i][j];  
            d.push(p);  
        }  
        while(!d.empty())d.pop();  
    }  

    for(int i=1;i<=n;i++)  
    {  
        lxy p;p.ip=m;p.n=h[i][m];  
        d.push(p);  
        for(int j=m-1;j>=1;j--)  
           {  
                if(a[i][j]!=a[i][j+1])  
                  d.top().n=0;  
                while(!d.empty()&&h[i][j]<=d.top().n)  
                  d.pop();  
                if(d.empty())  
                  r[i][j]=m-j;  
                else   
                  r[i][j]=d.top().ip-j-1;  
                p.ip=j;p.n=h[i][j];  
                d.push(p);  
           }  
        while(!d.empty())d.pop();  
    }  
    for(int i=1;i<=n;i++)  
      for(int j=1;j<=m;j++)  
      {  
          ans1=max(ans1,(l[i][j]+1+r[i][j])*h[i][j]);  
          int t=min(l[i][j]+1+r[i][j],h[i][j]);  
          ans2=max(t*t,ans2);  
      }  
    printf("%d\n%d",ans2,ans1);  
}   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值