题目大意:

有一个n*m矩阵,问我们从中选择r行c列的子矩阵。我们定义cost如下:

相邻的cost:同一行之间相邻的元素做差的绝对值,同一列之间相邻的元素做差的绝对值,

所有相邻的产生的cost加起来即为最后的cost.

注意:(1,2)和(1,3)已经计算了cost,那么(1,3)和(1,2)之间不要重复计算。

问怎么选择子矩阵可以使得cost最小。

n<=16,m<=16

解题思路:

这题首先我们能想到的是利用DP,我们可以把问题简化,假如r=1,那么应该怎么做呢?

我们可以定义状态memo[i][j],其中i表示我们选择第i列,j表示我们总共需要选几列,在这种状态下,我们得到的最小的cost。

for (k = j; k<i;k++)

memo[i][j]=min(memo[k][j-1]+ hc[k][i])

其中hc[a][b],表示相邻的a,b两列的cost。

知道这个之后,我们可以开使枚举多行,并把这个DP转移拓展为多行的情况,其实拓展为多行之后的DP转移和上面差不多。只是,变为

for (k = j; k<i;k++)

memo[i][j]=min(memo[k][j-1]+ hc[k][i]) + lc[i]

其中lc[i]表示,选择这一列之后行之间的cost大小。

废话:

这告诉我们,问题我们需要从简单情况开始下手,不要以为情况弱智就觉得没必要讨论,另外我们也见到了这种暴力加DP的组合。

#include <bits/stdc++.h>
using namespace std;
const int MAXN=17;
int chess[MAXN][MAXN];
int n,m,r,c;
vector<int> row;
int lc[MAXN];
int hc[MAXN][MAXN];
int finans;
const int INF=1e8;
int memo[MAXN][MAXN];
int dp(int chs,int ned){
if(ned==1)return lc[chs];
if(memo[chs][ned]!=-1)return memo[chs][ned];
int ret=INF;
for(int nc=ned-2;nc<chs;nc++){
ret= min(dp(nc,ned-1)+hc[nc][chs],ret);
}
ret+=lc[chs];
return memo[chs][ned]=ret;
}
void dfs(int level,int need,int idx){
if(level==need){
for(int i=0;i<m;i++){
int sum=0;
for(int j=0;j<(int)row.size()-1;j++){
sum+=abs(chess[row[j]][i]-chess[row[j+1]][i]);
}
lc[i]=sum;
}
for(int i=0;i<m;i++){
for(int j=i+1;j<m;j++){
int sum=0;
for(int rr=0;rr<(int)row.size();rr++){
sum+=abs(chess[row[rr]][i] - chess[row[rr]][j]);
}
hc[i][j]=hc[j][i]=sum;
}
}
int ans=INF;
for(int lie=c-1;lie<m;lie++){
memset(memo,-1,sizeof(memo));
ans= min(ans,dp(lie,c));
}
finans=min(finans,ans);
return ;
}
for(int i=idx;i<n;i++){
row.push_back(i);
dfs(level+1,need,i+1);
row.pop_back();
}
}
int main(){
cin>>n>>m>>r>>c;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>chess[i][j];
}
}
finans=INF;
dfs(0,r,0);
cout<<finans<<endl;
return 0;
}