动态规划(最大子矩阵)

在DP问题中有一种叫最大子矩阵问题,刚好碰到了这一题,于是学习分享之。

让我们先来看一下题目:ZOJ Problem Set - 1074

最大子矩阵

题目分类:动态规划

题目大意:就是输入一个N*N的矩阵,找出在矩阵中,所有元素加起来之和最大的子矩阵。

例如在 在这里插入图片描述这样一个4*4的矩阵中,元素之和最大的子矩阵为 9 2  ,它们之和为15。

这是一个最大子矩阵问题,我们怎么来解决这个问题呢?任何问题都会有它的简化的问题,这是二维的数组,与之对应的,我们可以先尝试一下一维数组。

如果有一个一维数组a[n],如何找出连续的一段,使其元素之和最大呢?

例如有 1 ,2, -3, 4, -2, 5 ,-3 ,-1, 7, 4, -6 这样一个数组,那么显然 4, -2, 5, -3, -1, 7, 4 这个子数组元素之和最大,为4+(-2)+5+(-3)+(-3)+7+4=14。为找到一维数组的最大子数组,我们可以有以下方法。

  1. 穷举法
for (i = 0; i < n; i++)
{
	for (j = 0; j <= i; j++)
	{
		sum = 0;
		for (k = j; k <= i; k++)
			sum += a[k];
		if (sum > max)    max = sum;
	}
}

这种方法在n很大情况下,需要运行的次数非常多,有三层循环,所有在n很大的情况下不适用

  1. 带记忆的递推法
record[0] = 0;
for (i = 1; i <= n; i++)                    //用下标1~n来储存n个数
	record[i] = record[i - 1] + a[i];		//用record记录a[i]前i个的和
max = 0;
for (i = 1; i <= n; i++)
{
	for (j = 0; j < i; j++)
	{
		sum = record[i] - record[j];
		if (sum > max)    max = sum;
	}
}

这种方法的时间复杂度为iO(n2)

  1. 动态规划
    我们来分析一下最优子结构,若想找到n个数的最大子段和,那么要找到n-1个数的最大子段和,这就出来了。我们用b[i]来表示a[0]…a[i]的最大子段和,b[i]无非有两种情况:
    (1)最大子段一直连续到a[i]  
    (2)以a[i]为首的新的子段

由此我们可以得到b[i]的状态转移方程:b[i]=max{b[i-1]+a[i],a[i]}。
最终我们得到的最大子段和为
max{b[i], 0<=i<n}, 算法如下:

int MaxSubArray(int a[], int n)
{
	int i, b = 0, sum = b[0];	//此处sum不可赋值为0,因为有可能出现全部为负的情况
	for (i = 0; i < n; i++)
	{
		if (b > 0)                // 若a[i]+b[i-1]会减小
			b += a[i];			 // 则以a[i]为首另起一个子段
		else
			b = a[i];
		if (b > sum)
			sum = b;
	}
	return sum;
}

说了这么多,这跟最大子矩阵有什么关系呢?当然有关系学啦!二维就是一维的扩展,把二维压扁不就变成一维了吗?

我们假设所求N*N的矩阵的最大子矩阵是从i列到j列,q行到p行,如下图所示(假设下标从1开始)
在这里插入图片描述
最大子矩阵就是图示红色部分,如果把最大子矩阵同列的加起来,我们可以得到一个一维数组{a[q][i]+······+a[p][i] , ······ ,a[q][j]+······+a[p][j]} ,现在我们可以看出,这其实就是一个一维数组的最大子段问题。如果把二维数组看成是纵向的一维数组和横向的一维数组,那问题不就迎刃而解了吗?把二维转换成了我们刚刚解决了的问题。

#include <iostream>
#include <cstring>
using namespace std;

int maxsub(int a[], int n)
{
	int i, max = a[0], b = 0;
	for (i = 0; i < n; i++)
	{
		if (b + a[i] >= a[i])	//当n-1的最大字段和+a[i]>=原来的最大字段和
			b += a[i];	//则n的最大字段和为b+a[i]
		else	//否则,就是a[i]
			b = a[i];
		if (b > max)	//找到最大的最大字段和
			max = b;
	}
	return max;
}

int main()
{
	int n, i, j, k, maxsubrec, maxsubarr, m;
	int dp[101][101], arr[101];
	cin >> n >> m;
	for (i = 0; i < n; i++)
		for (j = 0; j < m; j++)
			cin >> dp[i][j];
	maxsubrec = dp[0][0];
	for (i = 0; i < n; i++)	//i用于标识从哪行开始,依次选择行
	{
		memset(arr, 0, sizeof(arr));
		for (j = i; j < n; j++)	//从第i行开始,j为选择几行
		{
			for (k = 0; k < m; k++)	//从1列选择到m列,压缩行
				arr[k] += dp[j][k];
			maxsubarr = maxsub(arr, m);	//从每一个压缩行中选择最大字段和
			if (maxsubarr > maxsubrec) maxsubrec = maxsubarr;
		}
	}
	cout << maxsubrec << endl;
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值