BZOJ1057 棋盘制作 单调栈


参考博客:

http://blog.csdn.net/jzq233jzq/article/details/71438739



题意 :

给你一个0/1矩阵,找出最大的子矩阵。子矩阵的要求是任意相邻的两个数字必须不同。

思路:

看了题解想到可以转化成最大全0/1子矩阵,用到了单调栈活着悬线法。(悬线法没学会。。

首先是转化:就是把横纵坐标之和为基数的(偶数也可以,看心情,今天心情不好,我要基数)就把1变0,0变1。

接下来单调栈找出最大全..子矩形:

通过这篇文章学到了单调栈的基本原理http://calify.lofter.com/post/1d2d563b_9b67539     

感觉单调栈好神奇啊,然而为了啃这个单调栈已经很久没能A掉一道题目了,啃了一个星期。


简单说来就是

将给的数据依次压入栈中,当要压入的数据  < 栈顶的数据 , 就准备弹出栈顶元素,但是在弹出的时候要更新一下栈顶数据(作为一条边)的面积,再更新一下上域(指的是向上可以扩到哪里)

        (PS :明白上域的可以略过这里~

        我一开始想的是两次for确定一个点,再来两次for看看能往上往下都可以扩展到多远。n^4,n是2000,

        就是 8 * 10^12.我不知道20s的时限会不会超时,所以就去学了一下单调栈。比如:



1 1 1 0 0
1 1 1 1 0
1 1 1 0 0
1 1 1 1 1
1 1 0 0 0
1 1 0 0 0
1 1 0 0 0

转化成

3 2 1 0 1
4 3 2 1 0
3 2 1 0 0
5 4 3 2 1
2 1 0 0 0
2 1 0 0 0
2 1 0 0 0
第三行第一列往上可以扩展到第一行,往下可以扩展到第四行,面积3 * 4 = 12.

而单调栈只需要一次,因为当你第三行的3想进去,4就要弹出来,弹出来就要马上将要压入的第三行的数据3的行存为2而不是3. 这样当第五行的2进来迫使第三行的3弹出去的时候就是(5 - 2)*3,这里的3行指的是2-4行,因为4行弹出去的时候3是小的,3可以扩招到4的那一行,所以将3的行存为2,就相当于往上扩展了1行。

那可是正确的应该是网上扩展两行啊,这里因为第一行有一个相同的3,所以用第五行的2的行(5)去减第一行的3的行(1)就是4,代表着3可以扩展4行,这里考虑会不会落下情况,答:不会!因为有相同的数据会使得最终得到正确的扩展行,如果数据是唯一的,他扩展的行也是不需要再网上扩展的,因为,数据唯一,代表着第一行是0,1,2,4这四个数字中的一个,如果是0,1,2就没必要再往上扩展,如果是4,就更不用扩展了,因为到时候3会到栈顶(里面只有3,此时之前弹出的是两个4)就是说3的行为1,再继续,加入行为4的5,再继续加入第五行的2时,就要弹出啦,弹出3的时候就是3 * (5 - 1) = 12.)-----------PS结束

现在有了上下扩展域,就可以计算了。


坑点是:

1.需要加一个0来使每一列彻底弹出(按照一列一列来更新的)

2.还要注意按照连续的1来计算一次,按照连续的0来计算一次。




哎,实在抱歉没有描述清楚。

对不起大家。



上代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2050;
int n, m;
bool a[N][N];
int sum = 0;
int ans1 = 0;
int ans2 = 0;
int b[N][N];
struct Node
{
    int k;
    int x;
} s[N];

inline int fang(int x)
{
    return x * x;
}

inline void work(int k, int x)
{
    int p = k;
    for(; sum && x < s[sum].x; k = s[sum --].k)
    {
        ans1 = max(ans1, fang(min(s[sum].x, (p - s[sum].k))));
        ans2 = max(ans2, s[sum].x * (p - s[sum].k));
    }
    s[++sum].k = k;
    s[sum].x = x;
}

int main()
{
//    freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    memset(a, 0, sizeof(a));
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)
        {
            scanf("%d", &a[i][j]);
            if((i + j)&1)
            {//                a[i][j] = ~a[i][j];
                a[i][j] ^= 1;
            }
            getchar();
        }
    }
//    printf("\n");
    memset(b, 0, sizeof(b));
    for(int i = 1; i <= n; i ++)
    {
        for(int j = m; j >= 1; j --)
        {
            b[i][m + 1] = 0;
            b[i][j] = a[i][j] ? b[i][j + 1] + 1 : 0;
        }
    }
    for(int j = 1; j <= m; j ++)
    {
        work(n + 1, 0);
        sum = 0;
        for(int i = 1; i <= n; i ++)
        {
            work(i, b[i][j]);
        }
    }

    memset(b, 0, sizeof(b));
    for(int i = 1; i <= n; i ++)
    {
        for(int j = m; j >= 1; j --)
        {
            b[i][m + 1] = 0;
            b[i][j] = a[i][j] == 0? b[i][j + 1] + 1 : 0;
        }
    }
    for(int j = 1; j <= m; j ++)
    {
        work(n + 1, 0);
        sum = 0;
        for(int i = 1; i <= n; i ++)
        {
            work(i, b[i][j]);
        }
    }
    printf("%d\n%d\n", ans1, ans2);
} 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值