最大子矩阵和(任意子阵,O(n^4)和O(n^3))

引言

最近在想怎么更快地去求一个矩阵的任意子阵的最大和,看了一些别人的方法,现在自己总结一下,语言尽可能易懂,希望对大家有所帮助~


思路一

由HD 1559 改进而得,这题已知子阵的行列,具体可参看我的另一篇文章:

http://blog.csdn.net/feynman1999/article/details/57507631


代码示例:

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

int dp[1100][1100];//dp表示矩阵和(以现行列所表示点为右下角)

int MAX;//记录结果

int REMAX(int a,int b)
{
	return(a>b?a:b);
}

int main()
{
	int m,n,x,y,flag;//n,m为矩阵的行和列
	flag=0;
	MAX=-50001;
	memset(dp,0,sizeof(dp));
	while(cin>>n>>m)
	{
		for(x=1;x<=n;++x)
		for(y=1;y<=m;++y){
		for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j){
				if(flag==0)
                {
                    cin>>dp[i][j];//这里是本题的关键,dp暂存右下角元素
                    dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];//最后一项是暂存的右下角元素
                    //现在dp[i][j]是矩阵和了
                }
				if(i>=x&&j>=y)
				{
					MAX=REMAX(MAX,dp[i][j]-dp[i-x][j]-dp[i][j-y]+dp[i-x][j-y]);
				}
			}
			flag++;
		}

		cout<<MAX<<endl;
	}
	return 0;
}


这种思路的时间复杂度是O(n^4),可以看出这种代码在已知子阵的行列求最大固定行列子阵和时效率较高,而在求任意子阵时,是通过改变x,y行列实现的,这样显然有重复的地方。所以有更快的做法。


思路二

思路二的主要过程:

①确定子矩阵的上行和下行,即双重循环枚举出所有可能。

只确定了子矩阵的上下行是不行的,最大和子矩阵可能还在目前确定的子矩阵中(即更小的列数),因为行都能枚举到,只要再处理列就可以了。怎么处理列呢?这里就利用一个特性,对已经确定好上行和下行的子矩阵进行压缩。所谓压缩,就是对确定好的子矩阵每一列求和放到一个一维数组中。为什么这样可以呢?我是这样想的,行数是枚举的,包括了所有可能,对于每列,一定是要求和的,只是取多少列的问题,所以干脆转化成一维的问题,也就是最大子序列的问题,关于最大子序列可参看我的另一篇文章:

http://blog.csdn.net/feynman1999/article/details/56843878

③还有关键的一步就是压缩时求和的方法,如果每次压缩时求和,会费时间,于是一开始就借助一个辅助的矩阵aidmatrix,它的每个元素存的是原始矩阵第一行到该元素所在行 对应列的和,这样每次压缩时,只要给它上行和下行,对列进行扫描,就压缩好了~~就是这个样子:

for(int k=1;k<=n;++k){ //借助aidmatrix
                result[k]=aidmatrix[j][k]-aidmatrix[i-1][k];//对现在所选的两行之间的矩阵进行各列压缩
            }

④这样把压缩好的一维数组交给“最大子序列和”处理,就能返回当前上下行之间的最大子矩阵了,每次返回都进行筛选,即可得到最大子矩阵和了。


代码示例:

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

const int maxn=510;

int M[maxn][maxn];
int aid[maxn][maxn];
int result[maxn];

int main()
{
    ios::sync_with_stdio(false);
    int m,n,ans;
    cin>>m>>n;
    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j)
            cin>>M[i][j];

    ans=M[1][1];

    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j)
            aid[i][j]=aid[i-1][j]+M[i][j];

    for(int i=1;i<=m;++i)
        for(int j=i;j<=m;++j)
        {
            int sum=0;
            for(int k=1;k<=n;++k) result[k]=aid[j][k]-aid[i-1][k];
            for(int k=1;k<=n;++k){
                sum+=result[k];
                if(sum>ans) ans=sum;
                else if(sum<0) sum=0;
            }
        }

    cout<<ans<<endl;
    return 0;
}



这种做法,时间复杂度是O(n^3):枚举上下行是O(n^2),其中矩阵压缩和最大子序列均为O(n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值