P1169 [ZJOI2007]棋盘制作(单调栈)

原题: 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);
}


猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/86747947
今日推荐