首页 > 编程语言 >最大子矩阵(C/C++)

最大子矩阵(C/C++)

时间:2024-08-10 21:26:30浏览次数:14  
标签:最大 int 复杂度 矩阵 C++ presum x1 前缀

简介:

最大子矩阵问题是指在一个矩阵中找到一个子矩阵,使得该子矩阵的元素之和最大。

解决该问题的常用方法是使用动态规划。先计算出每一行的前缀和,然后对于每一列的起始和终止位置,计算出该区域内每一行的和,得到一个一维数组。再对该一维数组使用动态规划求解最大子数组和的问题,得到最大子矩阵的元素之和。

该问题也可以使用暴力搜索的方法,枚举所有可能的子矩阵,计算它们的元素之和,并找到最大值。但是由于时间复杂度过高,所以在实际应用中很少使用。

下面我们将以例题的形式时间复杂度由高到底给大家讲解几种方法。

例题 P1719 最大加权矩形

题目描述

为了更好的备战 NOIP2013,电脑组的几个女孩子 LYQ,ZSC,ZHQ 认为,我们不光需要机房,我们还需要运动,于是就决定找校长申请一块电脑组的课余运动场地,听说她们都是电脑组的高手,校长没有马上答应他们,而是先给她们出了一道数学题,并且告诉她们:你们能获得的运动场地的面积就是你们能找到的这个最大的数字。

校长先给他们一个 n×n 矩阵。要求矩阵中最大加权矩形,即矩阵的每一个元素都有一权值,权值定义在整数集上。从中找一矩形,矩形大小无限制,是其中包含的所有元素的和最大 。矩阵的每个元素属于 [−127,127] ,例如

 0 –2 –7  0 
 9  2 –6  2
-4  1 –4  1 
-1  8  0 –2

在左下角:

9  2
-4  1
-1  8

和为 15。

几个女孩子有点犯难了,于是就找到了电脑组精打细算的 HZH,TZY 小朋友帮忙计算,但是遗憾的是他们的答案都不一样,涉及土地的事情我们可不能含糊,你能帮忙计算出校长所给的矩形中加权和最大的矩形吗?

输入格式

第一行:n,接下来是 n 行 n 列的矩阵。

输出格式

最大矩形(子矩阵)的和。

输入 
4
0 -2 -7 0
 9 2 -6 2
-4 1 -4  1 
-1 8  0 -2
输出 
15
说明/提示

1≤n≤120


一、暴力求解

所谓的暴力求解就是分别枚举(x1,y1)、(x2,y2),四个for循环分别枚举每一个点的坐标,两个for循环去遍历这个矩阵的每一个点的权值,所以时间复杂度再O(n^6)。

代码实现:
#include<iostream>
using namespace std;
const int N=125;
int n,sum,maxx;
int a[N][N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
	}
	for(int x1=1;x1<=n;x1++){//枚举左上点
		for(int y1=1;y1<=n;y1++){
			for(int x2=x1;x2<=n;x2++){//枚举右下点
				for(int y2=y1;y2<=n;y2++){
					sum=0;
					for(int i=x1;i<=x2;i++){//遍历x1-x2,y1-y2点的子矩阵
						for(int j=y1;j<=y2;j++){
							sum+=a[i][j];
						}
					}
					maxx=max(maxx,sum);
				}
			}
		}
	}
	cout<<maxx<<endl;
	return 0;
}

暴力求解时间复杂度相当的高了,要想解决此题,需要更加优化的方法。下面按照时间复杂度继续优化。


二、一维前缀和优化

一维前缀和优化是建立在暴击求解的基础上来利用前缀和实现对求子矩阵,优化掉一层for循环,时间复杂度O(n^5),在解决此题也不能通过,时间复杂度也是比较高的。前缀和二维也就是通过一维滚动数组来实现。此一维前缀和优化,一般是不会用的,网上对它介绍的也比较少,大家了解一下即可。

代码实现:
#include<iostream>
using namespace std;
const int N=125;
int n,sum,maxx;
int a[N][N],presum[N][N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
			presum[i][j]=presum[i][j-1]+a[i][j];
		}
	}
	for(int x1=1;x1<=n;x1++){//枚举左上点
		for(int y1=1;y1<=n;y1++){
			for(int x2=x1;x2<=n;x2++){//枚举右下点
				for(int y2=y1;y2<=n;y2++){
					sum=0;
					for(int i=x1;i<=x2;i++){//遍历x1-x2,y1-y2点的子矩阵
						sum+=presum[i][y2]-presum[i][y1-1];
					}
					maxx=max(maxx,sum);
				}
			}
		}
	}
	cout<<maxx<<endl;
	return 0;
}

三、二维前缀和优化

二维前缀和优化就是在求解此题时,提前把二维前缀和求出来,在计算矩阵时,优化了两个for循环求解子矩阵值的问题,可以利用前缀和快速求出,二维前缀和优化时间复杂度为O(n^4)。

那么如何求解子矩阵的和呢?看下面这张图,要求(x1,y1)到(x2,y2)这个矩阵的值,那么前缀和presum[x][y]是由起点(1,1)到(x,y)的值,如何转换成起点为(x1,y1)呢,很简单,如图求红色的矩阵的值,=整个大矩阵-黄色矩阵-绿色矩阵-蓝色矩阵。那么就可以理解为presum[x2][y2]=A+B+C+D,A矩阵的起点是(1,1),所以黄色矩阵A也可以表示为presum[x1][y1],那么我们将黄色矩阵A合并给B跟C,这样起点就是(1,1)了,可以用前缀和表示了。那么红色矩阵D=presum[x2][y2]-(A+B)-(A+C)+A,我们将它转化为前缀和的形式,注意不能取到顶点,那么D=presum[x2][y2]-presum[x1-1][y2]-presum[x2][y1-1]+presum[x1-1][y1-1]

代码实现:
#include<iostream>
using namespace std;
const int N=125;
int a[N][N],presum[N][N];
int n,maxx;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
			presum[i][j]=presum[i][j-1]+presum[i-1][j]-presum[i-1][j-1]+a[i][j];//递推关系实现
		}
	}
	for(int x1=1;x1<=n;x1++){
		for(int y1=1;y1<=n;y1++){
			for(int x2=x1;x2<=n;x2++){
				for(int y2=y1;y2<=n;y2++){
					int sum=presum[x2][y2]-presum[x2][y1-1]-presum[x1-1][y2]+presum[x1-1][y1-1];//求子矩阵和
					maxx=max(maxx,sum);
				}
			}
		}
	}
	cout<<maxx<<endl;;
	return 0;
}

四、dp版

前缀和优化只是过度,动态规划才是归宿,这道题我们的最终解法就是动态规划,通过对行(列)的状态压缩,以及递推关系式实现对列(行)的优化,这样可以再优化掉一层for循环,时间复杂度为O(n^3)。

首先我们利用状态压缩,这里代码以列的状态压缩。第一行到最后一行是递增的,把每一列看成一维数组,多个列就成了二维的数组了。在求解时,先枚举起实行跟终止行,再去枚举每一列,这样就确定了多个子矩阵,把它用dp数组表示,每一个小子矩阵还可以与相邻的子矩阵构成子矩阵,每一次与自己比较大小。

代码实现:
#include<iostream>
using namespace std;
const int N=125;
int a[N][N],presum[N][N],dp[N];
int n,maxx;
//dp解法:状态压缩,把二维压缩成一维
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
			presum[i][j]=presum[i-1][j]+a[i][j];//列累加,列的前缀和
		}
	}
	for(int x1=1;x1<=n;x1++){//x1为起始行
		for(int x2=x1;x2<=n;x2++){//x2为终止行
		//第x1行到第x2行的最大子矩阵和
			for(int j=1;j<=n;j++){//j为列
				dp[j]=presum[x2][j]-presum[x1-1][j];
			}
			for(int i=1;i<=n;i++){
				dp[i]=max(dp[i],dp[i-1]+dp[i]);//要么自己为子矩阵跟自己跟上一个联合起来为子矩阵
				maxx=max(dp[i],maxx);
			}
		}
	}
	printf("%d",maxx);
	return 0;
}

标签:最大,int,复杂度,矩阵,C++,presum,x1,前缀
From: https://blog.csdn.net/m0_73633807/article/details/140718644

相关文章

  • 【C++】protobuf的简单使用(通讯录例子)
    protobuf的简单使用(通讯录例子).proto文件的编写保留字段字段唯一编号protobuf的类型enum类型Any类型oneof类型map类型完整通讯录代码.proto文件write文件read文件运行结果.proto文件的编写syntax用于指定protobuf的语法;package当.proto文件编译后再*.pb.h文件中会......
  • LeetCode 算法:最小栈 c++
    原题链接......
  • 【无人机通信】K-means聚类和粒子群优化最大限度地覆盖无人机辅助地面设备地面区域和
    ✅博主简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,Matlab项目合作可私信或扫描文章底部QQ二维码。......
  • c++中内存管理
    一、内存划分1、分区介绍(1)栈栈又称做堆栈,用于存储非静态局部变量、函数参数、返回值等,栈的空间是向下增长的。(2)内存映射段内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(3)堆堆用于程序运行时动态内存分......
  • 【C++面向对象】重载
    重载简述重载是C++面向对象编程领域的重要概念。C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。对于重载函数而言,有一个基本原则:重载的函数两两之间的参数列表互不相同。参数列表不同是指参数的数量不同,或者参数的类型不同,但C++并......
  • 扩展【从0制作自己的ros导航小车】C++_ROS_QT5联合编译,简单界面为ROS开发增添交互
    从0制作自己的ros导航小车前言一、环境搭建二、联合编译三、测试前言前面已经实现了导航功能,对于之后的一些开发,有交互能力是比较重要的,比如小车上连接一块屏幕,通过屏幕来选择模式,可视化等等。QT是不错的选择,但是需要做一些额外的工作,让QT与ROS能够建立联系,实现通信......
  • 集合相似度c++
    初入新蒟蒻一多多关照。弱弱问一句,有没有东营区一中的学哥学姐                               集合相似度题目是这样的——题目描述给定两个整数集合,它们的相似度定义为:Nc/Nt×100%。其中Nc是两个集合......
  • C++基础入门
    一·命名空间(namespace)正常namespace的使用include<stdio.h>#include<stdlib.h>//1.正常的命名空间定义//wzh是命名空间的名字,⼀般开发中是⽤项⽬名字做命名空间名。namespacebit{//命名空间中可以定义变量/函数/类型intrand=10;intAdd(in......
  • 1.动手编写第一个makefile编译c++多文件项目
    1.动手编写第一个makefile编译c++多文件项目1.1ubuntu开发环境安装•apt-getupdate#更新安装源•apt-getinstallg++#安装gcc和c++的开发库•apt-getinstallgdb#调试工具•apt-getinstallmake•apt-getinstallopenssh-server#远程连接工具•apt-getin......
  • 2024年华为OD机试真题-推荐多样性-C++-OD统一考试(C卷D卷)
    2024年OD统一考试(D卷)完整题库:华为OD机试2024年最新题库(Python、JAVA、C++合集) 题目描述:推荐多样性需要从多个列表中选择元素,一次性要返回N屏数据(窗口数量),每屏展示K个元素(窗口大小),选择策略:1.各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列......