[luogu]P2258 子矩阵[dp,枚举]

题目

子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。

例如,下面左图中选取第2、4行和第2、4、5列交叉位置的元素得到一个2\times 3的子矩阵如右图所示。

\begin{matrix} &9 &3 &3 &3 &9\\ &9 &4 &8 &7 &4\\ &1 &7 &4 &6 &6\\ &6 &8 &5 &6 &9\\ &7 &4 &5 &6 &1 \end{matrix}的其中一个2\times 3的子矩阵是\begin{matrix} &4 &7 &4\\ &8 &6 &9 \end{matrix}

相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。

矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。

本题任务:给定一个n行m列的正整数矩阵,请你从这个矩阵中选出一个r行c列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。

数据范围:1 \leq n,m \leq 16, 1 \leq a_{ij} \leq 1000, 1 \leq r \leq n, 1 \leq c \leq m

dp策略

由于n,m值较小, 首先想到枚举: 一个子矩阵可以看作n行选取r行,m列选取c列后取两两相交的元素构成的矩形, 故直接枚举的时间复杂度为O \( C_{n}^{r} * C_{m}^{c}\), 直接GGヘ(;′Д`ヘ) 

进行分析后可以看出,本题二维上不具有最优子结构,但只考虑单独的列或行具有最优子结构,故可以先枚举行然后进行dp,时间复杂度为O \( C_{n}^{r}*T_{dp} \),T_{dp}为选定行后dp的时间复杂度.

选定r行后,令dp[pos][k]代表考虑1\sim pos列选取k列且必须选取pos位的最优解的值

转移方程: dp[pos][k]=min \{ dp[j][k-1] + row[j][pos] \} + col[pos]\(k \leq pos \leq m , k-1 \leq j < pos\)

col[i]表示选取第i列后列的贡献,row[i][j]表示选取第i,j列后行的贡献

dp算法是2D/1D的,时间复杂度为T_{dp}=O \( M^{2}*C \),故整体代码时间复杂度为O\( C_{n}^{r}*M^{2}*C \)

#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]);
}

猜你喜欢

转载自blog.csdn.net/Hardict/article/details/82382439