[洛谷P1169] [ZJOI2007]棋盘制作

洛谷题目链接:[ZJOI2007]棋盘制作

题目描述

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

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

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

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

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

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:

3 3
1 0 1
0 1 0
1 0 0

输出样例#1:

4
6

说明

对于\(20\%\)的数据,\(N, M ≤ 80\)

对于\(40\%\)的数据,\(N, M ≤ 400\)

对于\(100\%\)的数据,\(N, M ≤ 2000\)

题意: 给出一个\(n*m\)\(01\)矩阵,要求出其中最大的\(01\)相间的正方形和矩形.

题解: 正方形还是比较好求的,如果不会可以先看看这道题:最大正方形II.

但是如果变成了矩形,DP在这里就不行了,因为在转移状态的时候无法记录长和宽.

所以这里引入了一个神奇的算法:单调栈.

单调栈是什么呢?

说简单点,就是一个栈,栈内元素是单调(递增或递减)的.

那么它能干什么呢?

它支持查询一个元素后面比它大/小的第一个元素的大小.

那么要怎么样实现查询的过程呢?

假设我们要维护一个单调上升的栈,这里我们拿一个数列举个栗子:a={2, 4, 1, 3, 2}

  • 首先栈为空,2入栈
  • 然后处理4,此时检查栈顶与该元素的大小关系,发现2<4,4可以直接入栈.
  • 处理1,此时栈顶4比1大,先记录4右边第一个比它小的元素是1,然后将4弹出栈,此时栈顶2仍然大于1,记录2右边第一个比它小的元素是1,将2弹出栈,最后将1入栈.
  • 处理3,栈顶1<3,直接将3入栈.
  • 处理2,栈顶3>2,记录3右边第一个比它小的元素是2,将3出栈,将2入栈.
  • 此时所有元素都已经入栈完了,检查栈是否为空,此时栈内的元素右边没有比它小的元素.

根据这个过程模拟,可以在\(O(n)\)的时间内求出一个元素后面比它大/小的第一个元素的大小.

因为每个元素都只会入栈/出栈一次,所以复杂度是\(O(n)\)的.

那么跟这道题又有什么关系呢?

要求出一个合法的矩阵是很麻烦的,但是我们可以先将矩阵分成一个个长条.然后再动态计算可以得到的矩形的面积最大值.

我们可以先枚举一个行,那么在这一行中,每一列的一个点都可以向上延伸一定的长度(当然这个可以从上一行的状态直接推过来),也就是上面讲的一个个长条.显然如果先有一根长条,后面又进来一根短一点的矩形,那么之前那根较长的矩形也无法算进对后面的贡献中.

也就是说,我们如果维护一个单调上升的栈,那么当一个长条矩形要出栈的时候,要计算它和后面组成的最大面积的时候,可以直接用这个长条矩形的长度乘以两个矩形之间的间隔.

可能讲得不太清楚,可以自己再画图理解一下.我才不会说是我懒得画图了

#include<bits/stdc++.h>
using namespace std;
const int N = 2000+5;
#define x (h[stk[top]])
#define y (pos-stk[top-1]-1)
#define z (min(x, y))

int n, m, a[N][N], h[N], top = 0, stk[N], ans1 = 0, ans2 = 0;

int main(){
    //freopen("data.in", "r", stdin);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) cin >> a[i][j];
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            if(i > 1 && a[i][j] != a[i-1][j]) h[j]++;
            else h[j] = 1;
        }
        int pos = 1;
        while(pos <= m){
            top = 0, stk[top] = pos-1, stk[++top] = pos++;
            while(pos <= m && a[i][stk[top]] != a[i][pos]){
                while(top && h[pos] < h[stk[top]])
                    ans1 = max(ans1, z*z), ans2 = max(ans2, x*y), top--;
                stk[++top] = pos++;
            }
            while(top) ans1 = max(ans1, z*z), ans2 = max(ans2, x*y), top--;
        }
    }
    cout << ans1 << endl << ans2 << endl;
    return 0;
}
posted @ 2018-09-28 19:35  Brave_Cattle  阅读(631)  评论(4编辑  收藏  举报