【蓝桥杯】 历届试题 最大子阵(二维动态规划)—— 酱懵静

历届试题 最大子阵

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

输入格式
  输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。
  接下来n行,每行m个整数,表示矩阵A。
输出格式
  输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。
样例输入:
3 3
-1 -4 3
3 4 -1
-5 -2 8

样例输出:10

样例说明
取最后一列,和为10。

数据规模和约定
  对于50%的数据,1<=n, m<=50;
  对于100%的数据,1<=n, m<=500,A中每个元素的绝对值不超过5000。



---分割线---



分析:
看到这道题的第一反应应该会马上想到我们用鼠标在桌面上单机左键然后选中图标的那个场景。同样地投影过来,试想,假如把那些图标换成某些数字,然后你的任务是选中矩阵中数值之和最大的那个子矩阵并输出其结果。
这样解读一下这个题目,相信更加抽象化了一下大家的思维。

下面我们来回忆一个很关键的知识点,后面将用到这个知识(高中时期数学曾学过,全国卷应用大题第一题——亦即17题)。 数列,我们用Sn来表示一个数列的前n项之和,那么假如我们要求中间某几项之和的时候(假设求第m到第n项之和),我们如果在知道Sn和Sm的前提下,则这之和可以用sum=Sn-Sm来表示。

回到本题,我们的目的是求一个和最大的子阵,这个题的难度主要是在维度上的提升,但是我们的着手点也正可以从这里入手。首先,我们可以利用一层循环来进行一个纵向的统计(这时候只考虑纵向,即你可以把这个矩阵看成是一个只有一列的矩阵,那么你的动态求值其实就是在找一段连续的纵向子阵,然后再将这段的纵向之和存在某个一维的数组中。对的,这个过程其实就是在降维~)。
然后,在上面的这个循环中,我们再考虑横向的一个动态寻找最大值(即,这时你的任务就是在一段指定的连续序列中,寻找某段连续的子序列,然后使得这个子序列的值最大),这样一来,就把一个寻找值最大的二维矩阵的任务完成了。

细心的同学,我相信你们仔细看上面的这个双重循环过程,其实就和我们用鼠标在拉某个框时的过程如出一辙。果然,艺术源于生活,算法也是。



具体步骤:
首先,我们在录入原二维数组map的同时,也录入一个对于某列而言的前n行二维数组cmap,即:
for(i=1;i<=n;i++)	//注意该二维的规格为n×m(n行m列)
	for(j=1;j<=m;j++)
	{	cin>>map[i][j];
		cmap[i][j]=cmap[i-1][j]+map[i][j];	}//注意此前需要将cmap全初始化0

注意:这里可以把cmap这个数组的每一行都看成是其前n行之和
(这里即用到了前面说到的数列的知识)

到此,我们来实现这个寻找最大矩阵的过程
我们可以先用一层循环i,来对cmap进行0到n层的扫描
然后在这里面,再用一层循环j来对cmap进行i+1到n层的扫描
这里扫描的目的是实现在第j层(当前层)减去前i层而得到的前j-i行之和
所以为了得到每一个前j-i之和在这j循环里面还需再用一个k循环来实现对某行的全部列进行减法运算
我把这个减法运算得到的结果放进另一个临时的一维数组rmap里面:rmap[k]

rmap[k]=cmap[j][k]-cmap[i][k]

当上面这一步k循环结束之后,即表示你得到了第i+1层到第j层的这中间的j-i行之和
即:上面这一步骤得到了纵向的一个最优贪心(即子行和的操作)
但是最终的最优结果是需要两个维度共同决定的,于是我们接下来要去找,在上面一步执行后(依然在最里层循环内)的前提下,横向的贪心。此时,可利用一个k循环,实现在从第一列到第m列中,寻找最大的子列(依然是在这个j循环内)。寻找方法是先用一个if语句判断,如果rmap[k-1]>0,那么要使当前某个rmap[k]最大只能是:rmap[k]+=rmap[k-1],否则不做变化
当上面的if语句执行后再用一个if语句对rmap[k]进行判断,如果rmap[k]>ans,则ans=rmap[k];
这里的ans即是最后的最大子阵和(即最终的输出),显然,这里需要提前把ans设置为一个很小的数
这里的很小其实只需要小于该二维阵中的单个元素的最小值即可
(注意哦,以上是在对求子行和的同时,也对子列和进行操作)

下面给出本题的完整代码:


---分割线---


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

int map[505][505];			//存储原始矩阵
int cmap[505][505];			//cmap[i][j]存储前i行的第j列之和 
int rmap[505];				//用于保存动态规划时某固定的多行的列之和 
int main()
{
	int n,m,i,j,k,ans=-5000;
	cin>>n>>m;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
		{
			cin>>map[i][j];
			cmap[i][j]=cmap[i-1][j]+map[i][j];	
		}
	for(i=0;i<=n;i++)
	{
		for(j=i+1;j<=n;j++)
		{
			for(k=1;k<=m;k++)
				rmap[k]=cmap[j][k]-cmap[i][k];	//完成纵向的贪心
			for(k=1;k<=m;k++)					//完成横向的贪心
			{
				if(rmap[k-1]>0)					//再次看出为什么数组要初始化为0
					rmap[k]+=rmap[k-1];
				if(rmap[k]>ans)
					ans=rmap[k];
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

发布了30 篇原创文章 · 获赞 67 · 访问量 3058

猜你喜欢

转载自blog.csdn.net/the_ZED/article/details/100097144