bzoj1057 [ZJOI2007]棋盘制作
[ZJOI2007]棋盘制作
Time Limit: 20 Sec Memory Limit: 162 MB
Description
国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源
于易经的思想,棋盘是一个88大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。而我们的主人公小Q,
正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小W决定
将棋盘扩大以适应他们的新规则。小Q找到了一张由NM个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种
颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。不过小Q还没有决定是找
一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他
希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是小Q找到了即将参加全
国信息学竞赛的你,你能帮助他么?
Input
第一行包含两个整数N和M,分别表示矩形纸片的长和宽。接下来的N行包含一个N * M的01矩阵,表示这张矩形
纸片的颜色(0表示白色,1表示黑色)。
Output
包含两行,每行包含一个整数。第一行为可以找到的最大正方形棋盘的面积,第二行为可以找到的最大矩形棋
盘的面积(注意正方形和矩形是可以相交或者包含的)。
Sample Input
3 3
1 0 1
0 1 0
1 0 0
Sample Output
4
6
HINT
N, M ≤ 2000
这真是一道好题啊(@隔壁的绝世好题)
这道题有三个真的是妙妙的地方(可能是我过于蒟蒻,各种简单的套路都没怎么用过。。。)
我们来细数一下本题的三个小trick
1:
首先,看到题目蒟蒻表示有点懵逼。。。。
阴阳互补?否极泰来?什么鬼????
所以直接颜色翻转一下。。。。。
问题变成了找最大的全为1(0)的矩阵。
一脸可做的样子233
2:
本题最主要的——悬线法
我们可以脑补一个画矩形的方法。
(嘿嘿嘿)
我们先找一个点,然后这个点向上拉,画出了一条线,对吧? (点动成线???)
然后这条线往左平移一波,再往右移动一波,这个扫过的面积就是矩形啦(线动成面???)
所以我们每个点记录三个信息(l, r, h) 表示这个点按照上面那样的操作后的最大高度和往左以及往右的最大平移距离。
3:
当你想到这么个妙妙的方法之后,你会发现,如果暴力更新的话,是个\(n^3\)的算法。。。
所以我们要来优化这个往左往右找距离的操作(我在说什么???)
然后要用单调栈。。。
关于单调栈。。。我可以瞎BB几句(我也不知道对不对。。。)
我们要的操作无非就是在一个数列中
$2, 5, 4, 6, 7, 6, 5 $
我们要找到\(5\)之前的第一个比他小的数。
而我们做的是每放入一个数,如果之前栈顶的数比他大,就弹出,直到能放进去为止。。。
这样为啥是对的呢?
这个栈里面是单调递增的,对吧?如果我们要找第一个比\(5\)小的数,一定没有被弹出,为什么呢?
因为弹出的数一定是左边和右边都比他小的数,如果这个数右边的数比5大,那么这个数一定比5大;反之则就是右边的数。(假装很有道理的样子~)
所以我们可以用这个东西优化一下就好了~(表示O(能过))
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 5;
struct lpl{
int h, l, r;
}data[maxn];
struct ld{
int h, num;
}lin;
int n, m, x, ans1, ans2;
int ini[maxn][maxn];
inline void putit()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
scanf("%d", &ini[i][j]);
}
inline void prepare()
{
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if((i + j) % 2 == 0)
ini[i][j] ^= 1;
for(int i = 1; i <= m; ++i) ini[0][i] = 911;
}
inline void search()
{
stack<ld> s1; lin.h = data[1].h; lin.num = 1; s1.push(lin); data[1].l = 1;
for(int i = 2; i <= m; ++i)
{
lin.h = data[i].h; lin.num = i;
while(!s1.empty() && s1.top().h >= lin.h) s1.pop();
if(s1.empty()) {s1.push(lin); data[i].l = 1;}
else {data[i].l = s1.top().num + 1; s1.push(lin);}
}
stack<ld> s2; lin.h = data[m].h; lin.num = m; s2.push(lin); data[m].r = m;
for(int i = m - 1; i >= 1; --i)
{
lin.h = data[i].h; lin.num = i;
while(!s2.empty() && s2.top().h >= lin.h) s2.pop();
if(s2.empty()) {s2.push(lin); data[i].r = m;}
else {data[i].r = s2.top().num - 1; s2.push(lin);}
}
}
inline void workk()
{
memset(data, 0, sizeof(data));
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= m; ++j)
{
if(ini[i][j] == x) data[j].h++;
if(ini[i][j] != x) data[j].h = data[j].l = data[j].r = 0;
}
search();
for(int j = 1; j <= m; ++j)
{
ans1 = max(ans1, (data[j].r - data[j].l + 1) * data[j].h);
int now = min(data[j].h, (data[j].r - data[j].l + 1));
ans2 = max(ans2, now * now);
}
}
}
int main()
{
putit();
prepare();
x = 0; workk();
x = 1; workk();
cout << ans2 << endl;
cout << ans1 ;
return 0;
}