矩阵前缀和应用2:求解(x, y)左上方k*k区域的最大加和 动态规划

矩阵前缀和:常数时间求一子矩阵的加和

如图,给定一点,要找到这点(包括)左上方的子区域内,使k*k区域所有数字加和最大的矩阵,如图 k=2
在这里插入图片描述

对于x,y左上方的子矩阵中的某一k*k区域,这个区域只能有四种状态:

  • k*k区域右边缘落在 (x, y) 正上方,问题转换为求解 (x-1, y) 左上方的最大加和的 k*k区域
  • k*k区域下边缘落在 (x, y) 正左方,问题转化为求解 (x, y-1) 左上方的最大加和的 k*k区域
  • k*k区域既不落在 (x, y)正上方,也不在正左方,问题转化为 (x-1, y) 或 (x, y-1) 左上方的最大加和的 k*k区域
  • k*k区域右下角就是 (x, y) ,那么问题可以直接被解

在这里插入图片描述

因为状态3包含于1,2之中,我们只需要比较状态124,取最大即可

定义状态:
dp[x][y] 表示【从左上角到x,y坐标】的子矩阵中,和最大的k*k区域的所有数字加和

状态转移

dp[x][y] = max(以xy为右下角的k*k区域加和, max(dp[x][y-1], dp[x-1][y]))

求解k*k区域所有元素的加和,通过矩阵前缀和,常数时间完成

代码

#include <bits/stdc++.h>

using namespace std;

int a[1509][1509], dp[1509][1509];
int m, n, k;

int kksum(int x1, int y1, int x2, int y2)
{
	return a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
}

int main()
{	
	cin>>m>>n>>k;
	memset(a, 0, sizeof(a));
	memset(dp, 0, sizeof(dp));
	
	// 计算行前缀和 
	for(int i=1; i<=m; i++)
		for(int j=1; j<=n; j++) 
			cin>>a[i][j], a[i][j]+=a[i][j-1]; 
	// 计算列前缀和 
	for(int i=1; i<=m; i++)
		for(int j=1; j<=n; j++)
			a[i][j] += a[i-1][j];
	
	for(int i=k; i<=m; i++)
		for(int j=k; j<=n; j++)
			dp[i][j] = max(kksum(i-k+1, j-k+1, i, j), max(dp[i][j-1], dp[i-1][j]));
	
	cout<<dp[m][n]<<endl;
	
	return 0;
}

/*
5 6 3
-1 -8 9 -123 123 3
77 9 18 -5 -33 9
-99 -77 321 -9 0 7
0 12 -11 0 85 54
0 1 -1 0 -1 0

4 4 2
0 0 0 0
0 1 1 0
0 1 1 0
0 0 0 0

3 4 2
-1 -2 3 -4
-5 6 -7 8
9 10 -11 -12

9 9 3
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 8 8 8 8 8 1 1 1
1 8 8 8 8 8 1 1 1
1 8 8 8 8 8 1 1 1
1 1 1 1 8 8 8 1 1
1 1 1 1 1 1 8 8 8
1 1 1 1 1 1 9 9 9
1 1 1 1 1 1 9 9 9
*/
发布了238 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/105007275