NCSTOJ 1134 蓝桥杯最大子阵

本文深入探讨了求解最大子矩阵和问题的算法,利用前缀和与动态规划技术,将二维问题转化为一维最大子序列和问题,提供详细的代码实现及优化思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NCSTOJ 1134 蓝桥杯最大子阵

Description

给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大。 其中,A的子矩阵指在A中行和列均连续的一块。

Input

输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。(1 ≤ n, m ≤ 500)接下来n行,每行m个整数,表示矩阵A。

Output

输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。

Sample Input

3 3
-1 -4 3
3 4 -1
-5 -2 8

Sample Output

10

前缀和+动态规划

关键是如何把二维的子序和转化为一维

LeetCode 53.一维最大子序和解答

用一个数sum累加,如果sum>0说明累加当前的数是会变大或变小的(可能是最优解的一部分),如果sum<0那么就让sum等于当前数(因为不可能是最优解,要重新开始),因为累加不肯能比单独现在这个数大


方法:

这里二维转化成一维一个重点就是一维子序和范围可以理解成矩阵中的子矩阵

   1  2  3
1 -1 -1  3
2  3  4 -1
3 -5 -2  8

我选择第三列3,-1,8作为一个一维子序和时,可以得到结果10,这也是一个子矩阵,因为不管一维二维都是需要连续,二维连续就是子矩阵

范围怎么选定?答案是通过累加,我们不需要知道选的是哪里,只需要知道这一部分和就可以了,如这个例子的最优解是第二列加第三列,计算时只需要把第三列加到第二列,比较答案即可

把二维的子矩阵转化为一维的列的最大子序和,通过枚举所有的列可能性得到答案,列中的所有可能一维子序和帮你枚举了


代码实现:

数据从下标1开始存,初始化为0,这样不用判断范围,也不用担心会影响结果,范围num[n][m]

枚举所有的列,需要两个控制变量,一个是开始范围的i也就是从第几列开始 i ∈ [ 1 , m ] i \in [1,m] i[1,m],然后是从i开始的移动变量j也就是从i开始要几列 j ∈ [ i , m ] j\in[i,m] j[i,m],用一个数组dp保存第k行的数据,然后累加第j列,转化为一维子序和求解,更新最大值

#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
static const auto io_sync_off = []() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    return nullptr;
}();

const int maxn = 505;
int num[maxn][maxn];
int dp[maxn];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            cin >> num[i][j];

    int ans = num[1][1]; // 初始化
    for (int i = 1; i <= m; ++i)
    {
        memset(dp, 0, sizeof(dp)); // 每次从不同行开始时要清零
        for (int j = i; j <= m; ++j)
        {
            for (int k = 1; k <= n; ++k)
                dp[k] += num[k][j]; // 累加第j列
            int mcur = dp[1], cur = 0; // 求一维最大子序和
            for (int k = 1; k <= n; ++k)
            {
                if (cur > 0)
                    cur += dp[k];
                else
                    cur = dp[k];
                mcur = max(mcur, cur);
            }
            ans = max(mcur, ans);
        }
    }
    cout << ans;
    return 0;
}

上述代码没有问题,但是求和的时候看着稍微有些累赘,并且要维护一个dp数组,仔细想想是可以通过前缀和得到某一列的值的,所以在输入数据时将其维护成前缀和数组,这样可以减少一个循环(按说时间因该没变化,但是更慢了wtf???)

要求某一块列的和,那么我们要对行累加(其实不是求列和,而是得到这一列到前面某一列范围内的每行和,就是上一个dp数组,越说越乱,手动模拟一下就好了)

#include <vector>
#include <iostream>
using namespace std;
static const auto io_sync_off = []() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    return nullptr;
}();

const int maxn = 505;
int dp[maxn][maxn];

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
        {
            cin >> dp[i][j];
            dp[i][j] += dp[i][j - 1]; // 求i行前缀和
        }

    int ans = dp[1][1];
    for (int i = 1; i <= m; ++i) // 枚举列
        for (int j = i; j <= m; ++j) // 要几列
        {
            int cursum = 0;
            for (int k = 1; k <= n; ++k) // 求j-i列之和组成的一维子序和最优解
            {
                if (cursum > 0)
                    cursum += dp[k][j] - dp[k][i - 1];
                else
                    cursum = dp[k][j] - dp[k][i - 1];
                ans = max(cursum, ans);
            }
        }
    cout << ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值