多维度求解最大子矩阵和(前缀+压缩+DP)

求解此类问题要找规律,首先看一下维度为1时的该问题。
①当维度为1时。
此问题变为求最大连续子段和。此时可以用动态规划来解决该问题。
动态规划的思路也很简单
步骤 1:令状态 dp[i] 表示以 A[i] 作为末尾的连续序列的最大和(这里是说 A[i] 必须作为连续序列的末尾)。
步骤 2:做如下考虑:因为 dp[i] 要求是必须以 A[i] 结尾的连续序列,那么只有两种情况:
这个最大和的连续序列只有一个元素,即以 A[i] 开始,以 A[i] 结尾。
这个最大和的连续序列有多个元素,即从前面某处 A[p] 开始 (p<i),一直到 A[i] 结尾。
  对第一种情况,最大和就是 A[i] 本身。
  对第二种情况,最大和是 dp[i-1]+A[i]。
  于是得到状态转移方程:
        dp[i] = max{A[i], dp[i-1]+A[i]}
那么求最大连续子段和的核心代码为:

for (int i=1;i<=n;i++){
  dp[i]=max(a[i],a[i]+dp[i-1]);
  ans=max(ans,dp[i]);
  }
  cout<<ans<<endl;

维度为1时非常简单,那么维度为2时该怎么办呢?
②当维度为2时。
问题变为在一个矩阵中,找一个他的子矩阵,该子矩阵是所有子矩阵中和最大的。首先我们想到暴力枚举,那么复杂度便为O(N * N * M * M * Sum),其中若不优化,要求Sum也是很困难的。
那么首先让我们使用前缀来优化求解Sum的时间复杂度。
求其中子矩阵的和,肯定需要多次询问。那么最好的解决办法就是前缀和。什么是前缀和?设a[i][j]为第j列前i行的和。我们有了求一维度最大连续子段和的经验,我们可以将维度拆开,枚举其中一维,然后用DP扫另一维,这样可以让复杂度降到O(N * M * M )
假设枚举x维度,核心代码

ll sqsum(int x1,int x2,int s){
	return  a[x2][s] - a[x1][s];
}
int main(){
	for(int j=1;j<=k;j++)
		for(int i=1;i<=c;i++)
			a[i][j] += a[i-1][j];
	for(int i=1;i<=k;i++)
			for(int j=i;j<=k;j++){
				memset(dp,0,sizeof(dp));
					for(int v =1 ;v<=g;v++){
						dp[v] = max(dp[v-1],0) + sqsum(i,j,v);
							 anss=max(anss,dp[v]);
							}
					}
		cout<<anss<<endl;
}

有了前两维,那么第三维就很容易了。
③当维度为3时。
有了前面的经验,当维度为3时,枚举两维中的所有矩阵,对第三维进行扫描DP即可。
这时候前缀和a[i][j][k]为在K维度下的a[0][j][k]~a[i][j][k]的和加上a[i][0][k] ~a[i][j][k] 的和。既然这样,那么再求以(x1,y1)为左上角坐标和(x2,y2)为右下角坐标的子矩阵的所有元素和。就变得简单了。这时候的和为 a[x2][y2] - a[x1-1][y2] - a[x2][y1-1] + a[x1-1][y1-1](此时a为前缀和的a)同样这样可以将最后一维度的O(n^2)的复杂度降为O(n),枚举的复杂度仍没有变,为O( N * N * M * M),总体复杂度为O(N^5)
核心代码

ll sqsum(int x1,int y1,int x2,int y2,int s){
	return a[x2][y2][s] + a[x1-1][y1-1][s]  - a[x1-1][y2][s]  - a[x2][y1-1][s] ;
}
int main(){
	for(int l=1;l<=g;l++)
		for(int i=1;i<=c;i++)
			for(int j=1;j<=k;j++)
				a[i][j][l] += a[i-1][j][l] + a[i][j-1][l] - a[i-1][j-1][l];
	for(int i=1;i<=c;i++)
			for(int j=1;j<=k;j++)
				for(int p = 1;p <= i;p++)
					for(int q = 1;q <= j;q++){
						memset(dp,0,sizeof(dp));
						for(int v =1 ;v<=g;v++){
							dp[v] = max(dp[v-1],0) + sqsum(p,q,i,j,v);
							 anss=max(anss,dp[v]);
						}
					}
		cout<<anss<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值