题目
子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第2、4行和第2、4、5列交叉位置的元素得到一个的子矩阵如右图所示。
的其中一个的子矩阵是
相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个n行m列的正整数矩阵,请你从这个矩阵中选出一个r行c列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
数据范围:
dp策略
由于值较小, 首先想到枚举: 一个子矩阵可以看作行选取行,列选取列后取两两相交的元素构成的矩形, 故直接枚举的时间复杂度为, 直接GGヘ(;′Д`ヘ)
进行分析后可以看出,本题二维上不具有最优子结构,但只考虑单独的列或行具有最优子结构,故可以先枚举行然后进行,时间复杂度为,为选定行后的时间复杂度.
选定行后,令代表考虑列选取列且必须选取位的最优解的值
转移方程:
表示选取第i列后列的贡献,表示选取第列后行的贡献
该算法是的,时间复杂度为,故整体代码时间复杂度为
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
int N, M, R, C;
int p[25][25];
int ans;
int r[25], cnt_r;
void dfs(int);
int main(){
ios::sync_with_stdio(false);
cin >> N >> M >> R >> C;
int i, j;
for(i = 1; i <= N; i++)
for(j = 1; j <= M; j++)
cin >> p[i][j];
ans = 1e9 + 7;
dfs(1);
cout << ans << endl;
return 0;
}
//选定R行后:col[i] 单独列的贡献, row[i][j] i列与j列行的总贡献
int col[25], row[25][25];
int dp[25][25];
void pre_dp();
void dp_ans();
void dfs(int x){
if(cnt_r == R){
//for(int i = 1; i <= cnt_r; i++) printf("%d\n", r[i]); printf("\n");
pre_dp();
dp_ans();
return;
}
if(R - cnt_r > N - x + 1) return;//剪枝
r[++cnt_r] = x;
dfs(x + 1);
--cnt_r;
dfs(x + 1);
}
void dp_ans(){
memset(dp, 100, sizeof(dp));
int i, j, k;
for(j = 1; j <= M; j++) dp[j][1] = col[j];
for(k = 2; k <= C; k++)
for(i = k; i <= M; i++){
for(j = k - 1; j < i; j++)
dp[i][k] = min(dp[i][k], dp[j][k - 1] + row[j][i]);
dp[i][k] += col[i];
}
for(j = C; j <= M; j++) ans = min(ans, dp[j][C]);
}
void pre_dp(){
int i, j, k;
memset(col, 0, sizeof(col));
memset(row, 0, sizeof(row));
for(j = 1; j <= M; j++)
for(i = 1; i < R; i++)
col[j] += abs(p[r[i]][j] - p[r[i + 1]][j]);
for(i = 1; i <= M; i++)
for(j = i + 1; j <= M; j++)
for(k = 1; k <= R; k++)
row[i][j] += abs(p[r[k]][i] - p[r[k]][j]);
}