【悬线法】解决最大子矩阵问题

学习博客: 【算法学习笔记】浅谈悬线法


  • 对于一般的最大子矩阵题目,框架大概是这样的:
const int N = 100010;
int n, a[N], l[N], r[N];
ll ans;
int main() {
    while (scanf("%d", &n) != EOF && n) {
        ans = 0;
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]), l[i] = r[i] = i; // l,r边界初始化一下
        for (int i = 1; i <= n; i++)
            while (l[i] > 1 && a[i] <= a[l[i] - 1]) l[i] = l[l[i] - 1]; // ***
        for (int i = n; i >= 1; i--)
            while (r[i] < n && a[i] <= a[r[i] + 1]) r[i] = r[r[i] + 1];
        for (int i = 1; i <= n; i++)
            ans = max(ans, (long long)(r[i] - l[i] + 1) * a[i]);
        printf("%lld\n", ans);
    }
    return 0;
}
  • / /   ∗ ∗   ∗ //~**~* //   对于普通的题目(博客后三题),悬线 i i i 的临时左边界是 l [ i ] l[i] l[i] ,但是当前悬线还有可能可以往左扩展,并且 l [ i ] − 1 l[i]−1 l[i]1 位置的悬线能向左扩展到的位置, i i i 位置的悬线一定也可以扩展到,于是我们以 l [ i ] − 1 l[i]-1 l[i]1 为跳板,将 l [ i ] l[i] l[i] 更新为 l [ l [ i ] − 1 ] l[l[i]-1] l[l[i]1]

  • 但是上述只是普通的题目,普通在只需根据 h [ i ] h[i] h[i] h [ l [ i ] − 1 ] h[l[i]-1] h[l[i]1] 的高度大小即可判断是否可以继续向左拓展。(高度比较体现在 h [ i ] < = h [ l [ i ] − 1 ] h[i] <= h[l[i] - 1] h[i]<=h[l[i]1] ,拓展体现在 l [ i ] = l [ l [ i ] − 1 ] l[i] = l[l[i] - 1] l[i]=l[l[i]1]

  • 但是对于一些题目,不仅要看高度,还要一些其他的限制,例如 棋盘制作,拓展条件不仅只有高度,还有拓展边界是否可拓展(体现在 h [ l [ j ] ]   ! = h [ l [ j ] − 1 ] h[l[j]] ~!= h[l[j] - 1] h[l[j]] !=h[l[j]1]


题目:

一维:洛谷SP1805 UVA1619

二维:P4147 玉蟾宫

其实都差不多,只是二维需要自己算一下高度。


P1169 [ZJOI2007]棋盘制作

AC代码(特意压了一下维,只有0.6MB hahaha):

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=2e3+10;

int a[N], h[N], l[N], r[N];

int main()
{
    int x;
    int n, m; scanf("%d %d", &n, &m);
    LL ans1 = 0, ans2 = 0;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++){
            scanf("%d", &x);
            if(i == 1) a[j] = x, h[j] = 1;
            else{
                if(a[j] ^ x) h[j] ++;
                else h[j] = 1;

                a[j] = x;
            }

            l[j] = r[j] = j;
        }

        for(int j=1; j<=m; j++)
            while(l[j] > 1 && (a[l[j]] ^ a[l[j] - 1]) && h[j] <= h[l[j] - 1]) l[j] = l[l[j] - 1];
        // 注意拓展条件限制更多
        
        for(int j=m; j>=1; j--)
            while(r[j] < m && (a[r[j]] ^ a[r[j] + 1]) && h[j] <= h[r[j] + 1]) r[j] = r[r[j] + 1];
        
        for(int j=1; j<=m; j++){
            LL len1 = r[j] - l[j] + 1, len2 = h[j];
            ans1 = max(ans1, min(len1, len2) * min(len1, len2));
            ans2 = max(ans2, len1 * len2);
        }
    }
        
    printf("%lld\n%lld", ans1, ans2);

	system("pause");
    
    return 0;
}

AC代码2:

  • DP思想,先预处理第 i i i 单行拓展边界,再通过 j j j 列悬线在 1 − > i − 1 1->i-1 1>i1 行拓展边界更新限制。
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=2e3+10;

int a[N][N], h[N][N], l[N][N], r[N][N];

int main()
{
    int x;
    int n, m; scanf("%d %d", &n, &m);
    LL ans1 = 0, ans2 = 0;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++) scanf("%d", &a[i][j]), h[i][j] = 1, l[i][j] = r[i][j] = j;

        for(int j=2; j<=m; j++)
            if(a[i][j] ^ a[i][j - 1]) l[i][j] = l[i][j - 1];
        
        for(int j=m - 1; j>=1; j--)
            if(a[i][j] ^ a[i][j + 1]) r[i][j] = r[i][j + 1];
        
        if(i >= 2)
            for(int j=1; j<=m; j++)
                if(a[i][j] ^ a[i - 1][j])
                    h[i][j] = h[i - 1][j] + 1,
                    l[i][j] = max(l[i][j], l[i - 1][j]),
                    r[i][j] = min(r[i][j], r[i - 1][j]);
            
        
        for(int j=1; j<=m; j++){
            int len1 = r[i][j] - l[i][j] + 1, len2 = h[i][j];
            if(len1 > len2) swap(len1, len2);
            
            ans1 = max(ans1, 1ll * len1 * len1);
            ans2 = max(ans2, 1ll * len1 * len2);

            // cout << i << " " << j << " " << len1 << " " << len2 << endl;
        }

        // for(int j=1; j<=m; j++) cout << h[i][j] << " "; cout << endl;
    }
        
    printf("%lld\n%lld", ans1, ans2);
    


    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值