如图,给定一点,要找到这点(包括)左上方的子区域内,使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
*/