原题: https://www.luogu.org/problemnew/show/P1169
题意:
01矩阵,找出最大的正方形和矩形,满足01相见。
这里有一个优化,因为01相见,且所选矩阵的左端点只有两种情况:
(x+y)%2==0||(x+y)%2==1
,所以可以将(x+y)%2==0
的点转置(01互换),那么接下来统计最大全0或全1子矩阵即可。
但是其实做起来优化不到哪里去。。
测试数据:
INPUT
5 5
1 1 1 1 1
1 0 1 0 1
1 1 0 1 0
1 1 1 0 1
1 1 1 1 1
OUTPUT
9
9
5 6
1 1 1 1 1 0
1 0 1 0 1 1
0 1 0 1 1 0
1 0 1 1 1 1
1 1 1 1 1 0
OUTPUT
9
9
INPUT
5 6
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
OUTPUT
1
1
INPUT
5 6
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
OUTPUT
25
30
INPUT
5 6
1 0 1 1 1 0
0 1 0 1 0 1
1 0 1 1 1 0
1 1 1 1 0 1
1 1 1 1 1 0
OUTPUT
9
10
INPUT
5 5
0 0 1 1 0
0 1 1 1 0
1 0 1 0 1
1 1 0 1 0
0 0 0 1 1
OUTPUT
4
8
简化版单调栈:
对每一列做单调栈,先维护好最大可以往左延多少。从上往下维护一个单调不减的单调栈。在可以连接(01相见)的情况下,当遇到往左延较短的,对于栈中的大的部分,局部做一次答案的维护(因为如果选择的矩形跑到其他的地方,就意味着在宽度方面不需要这部分那么宽)。
最后对这个栈进行一次答案的维护。这里遇到大的,不是直接弹出栈,而是弹出后,再压入一个和下一个相同长度的,要不然后面维护栈的时候会出错。
这种单调栈比较容易理解,但是如果是一个单调减的情况时间复杂度会很大。
优化的方法也比较简单,既然一段都相同,那进栈打上“数量”标记即可。
比较坑的是我两个输出放在一行居然判WA,一点都不智能…
#include<bits/stdc++.h>
using namespace std;
#define pill pair<int,int>
#define DEBU
int n, m;
int Mp[2001][2001];
int L[2001];
int sta[2001], top;
int deal() {
int ans = 0;
for(int i = 1; i <= top; i++) {
ans = max(ans, sta[i] * (top - i + 1));
}
return ans;
}
int dealf() {
int ansf = 0;
for(int i = 1; i <= top; i++) {
ansf = max(ansf, min(sta[i], top - i + 1) * min(sta[i], top - i + 1));
}
return ansf;
}
pill solve() {
int ans = 0;
int ansf = 0;
for(int j = 1; j <= m; j++) {
for(int i = 1; i <= n; i++) {
if(Mp[i][j] != Mp[i][j - 1])
L[i]++;
else
L[i] = 1;
}
top = 0;
for(int i = 1; i <= n; i++) {
if(i == 1) {
sta[++top] = L[i];
continue;
}
if(Mp[i][j] != Mp[i - 1][j]) {
if(L[i] >= sta[top]) {
sta[++top] = L[i];
} else {
stack<int>S;
while(sta[top] > L[i] && top)
S.push(sta[top]), top--;
sta[++top] = L[i];
while(!S.empty()) {
ans = max(ans, (int)(S.top() * S.size()));
ansf = max(ansf, min(S.top(), (int)S.size()) * min(S.top(), (int)S.size()));
S.pop();
sta[++top] = L[i]; // 压回
}
}
} else {
ans = max(ans, deal());
ansf = max(ansf, dealf());
top = 0;
sta[++top] = L[i];
}
}
if(top) {
ans = max(ans, deal());
ansf = max(ansf, dealf());
top = 0;
}
}
return {ansf, ans};
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
scanf("%d", Mp[i] + j);
}
}
pill ans = solve();
printf("%d\n%d\n", ans.first, ans.second);
}
标准O(n)单调栈:
我在这篇里面讲过
https://blog.csdn.net/jk_chen_acmer/article/details/80017428
这里主要是讲了关于进栈时是否弹出相同大小的点的问题:如果不弹出,则一系列相同大小的点第一个精准;弹出的话最后一个精准。并且只要有一个精准就不影响答案。
今天敲的时候遇到了写问题,所以我这边在总结一下。
目标是维护每个点往左和往右延伸的端点,栈为单调不减。
左端点: 每个点x(下标)进栈时,前面的剩下的都是比x大的点,那么就知道往左延伸的端点了。
右端点: 每个点出栈时,说明新进来的点比x大,那么右端点也知道了。
有个细节:进栈时,如果栈为空,即前面没有比x小的点,那么x的左端点该怎么定?
#include<bits/stdc++.h>
using namespace std;
int n, m;
int Mp[2001][2001];
int L[2001];
int sta[2001], top, l[2001], r[2001];
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
scanf("%d", Mp[i] + j);
}
}
int ans1 = 0, ans2 = 0;
for(int j = 1; j <= m; j++) {
// 单调不减栈 相同不出栈:相同长度时,只有第一个正确
sta[0] = 0;
top = 0;
for(int i = 1; i <= n; i++) {
if(Mp[i][j] == Mp[i][j - 1])
L[i] = 1;
else
L[i]++;
if(Mp[i][j] != Mp[i - 1][j]) {
while(top && L[sta[top]] > L[i]) {
r[sta[top]] = i - 1;
l[i] = l[sta[top]];//!!!
top--;
}
if(top)l[i]=sta[top]+1;
sta[++top] = i;
} else {
while(top) {
r[sta[top]] = i - 1;
top--;
}
l[i] = i;
sta[++top] = i;
}
}
while(top) {
r[sta[top]] = n;
top--;
}
for(int i = 1; i <= n; i++)
ans1 = max(ans1, min(L[i], r[i] - l[i] + 1) * min(L[i], r[i] - l[i] + 1)),
ans2 = max(ans2, L[i] * (r[i] - l[i] + 1));
}
printf("%d\n%d\n", ans1, ans2);
}