原题链接 :P2258 子矩阵
分析
题意很简单,定义一个矩阵的分值为所有相邻元素差的绝对值(每一对只计算一次)。
然后要求一个矩阵的分值最小子矩阵。
这里的子矩阵指选取r行,c列,交点组成的子矩阵。
50pts
普及题还是很良心的,部分分都给的很充足,打个爆搜50分就到手了。
55pts
爆搜加个最优性剪枝,能多5分orz。
100pts
矩阵不就考个dp吗……
不过打死也想不出状态转移方程,最后看了题解才知道。
首先要明确,dp不是指那种整个程序只有一个状态转移方程,然后forforfor就可以了的。
这里在dp之前我们可以通过枚举枚举掉行,然后再列上跑dp,状态转移方程就很好写了。
以下均为已经枚举好行之后的处理,默认行选择1,2,3行
(v[i])表示第i列纵向相邻差的绝对值和。
(g[i][j])表示第i行和第j行相邻时的绝对值和。
(f[i][j])表示现在需要选取第i列,包括这一列一共选了j列,最小的价值。
状态转移方程就很明确了。
[f[i][j]=$min{f[i][j],min{f[i-k][j-1]+v[i]+g[i-k][j]}}$
那么我们直接跑一边dp,就能求出当前最小值了。
然后答案就是所有最小值的最小值,直接跑个min就可以了。
详情见代码。
### 代码
#### 50pts
```cpp
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
char c;int num,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
while(c=getchar(), isdigit(c))num=num*10+c-'0';
return f*num;
}
int n,m,r,c,f[109][3],a[19][19];
int ans=0x7fffffff;
void check(){
int now=0;
for(int i=1;i<=r;i++)
for(int j=2;j<=c;j++)
now+=abs(a[f[i][1]][f[j][2]]-a[f[i][1]][f[j-1][2]]);
for(int i=2;i<=r;i++)
for(int j=1;j<=c;j++)
now+=abs(a[f[i][1]][f[j][2]]-a[f[i-1][1]][f[j][2]]);
/*if(now==10){
for(int i=1;i<=r;i++)printf("%d ",f[i][1]);printf("
");
for(int i=1;i<=c;i++)printf("%d ",f[i][2]);printf("
");printf("
");
}*/
ans=min(ans,now);
}
void dfs(int x,int y,int nr,int nc){
//if(f[1][1]==4&&f[2][1]==5&&f[1][2]==1&&f[2][2]==3&&f[3][2]==4)printf("1111
");
if(nc==c+1){check();return ;}
if((x>n&&nr!=r+1)||(y>m&&nc!=c+1))return ;
//printf("%d %d %d %d
",x,y,nr,nc);
if(nr==r+1){
for(int i=y;i<=m;i++){
f[nc][2]=i;
dfs(x,i+1,nr,nc+1);
}
return ;
}else {
for(int i=x;i<=n;i++){
f[nr][1]=i;
dfs(i+1,y,nr+1,nc);
}
}
}
int main()
{
n=read();m=read();
r=read();c=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]=read();
}
}
dfs(1,1,1,1);
printf("%d
",ans);
return 0;
}
/* 发现n,m都是16
* 感觉可以支持O(n^4)的算法
*
*/
```
#### 55pts(仅剪枝函数)
```cpp
bool Prune(int nr,int nc){
int now=0;
for(int i=1;i<nr;i++)
for(int j=2;j<nc;j++)
now+=abs(a[f[i][1]][f[j][2]]-a[f[i][1]][f[j-1][2]]);
for(int i=2;i<nr;i++)
for(int j=1;j<nc;j++)
now+=abs(a[f[i][1]][f[j][2]]-a[f[i-1][1]][f[j][2]]);
if(now>ans)return true;
else return false;
}
```
#### 100pts
```cpp
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
char c;int num,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
while(c=getchar(), isdigit(c))num=num*10+c-'0';
return f*num;
}
int n,m,r,c,a[19][19];
int line[19],f[19][19];
int v[19],g[19][19];
int ans=0x7fffffff;
void dp(){
memset(v,0,sizeof(v));
memset(g,0,sizeof(g));
memset(f,0x3f,sizeof(f));
for(int i=1;i<=m;i++)
for(int j=2;j<=r;j++)
v[i]+=abs(a[line[j]][i]-a[line[j-1]][i]);
for(int i=1;i<=m;i++)
for(int j=1;j<i;j++)
for(int k=1;k<=r;k++)
g[j][i]=g[i][j]=g[i][j]+abs(a[line[k]][j]-a[line[k]][i]);
/*for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
printf("%d ",g[i][j]);
}
printf("
");
}*/
for(int i=1;i<=m;i++)f[i][1]=v[i];
for(int i=1;i<=m;i++)
for(int j=1;j<=c;j++)
for(int k=1;k<i&&i-k>=j-1;k++)
f[i][j]=min(f[i][j],f[i-k][j-1]+v[i]+g[i-k][i]);
for(int i=c;i<=m;i++)
ans=min(ans,f[i][c]);
}
void dfs(int x,int d){
if(x==r+1){dp();return ;}
if(d>n)return ;
for(int i=d;i<=n;i++)line[x]=i,dfs(x+1,i+1);
}
int main()
{
//freopen("data.in","r",stdin);
n=read();m=read();
r=read();c=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=read();
line[1]=1;line[2]=2;line[3]=5;
dfs(1,1);
printf("%d
",ans);
return 0;
}
/*
* 先枚举行数,然后再已有行数的基础上进行dp求列数。
* f[i][j]表示前i行,已用j列,取最后一列,最小价值。
* f[i][j]=min(f[i][j],min{f[i-k][j-1]+v[i]+g[i-k][i]});
* 预处理v[i]表示当前选i行的代价
* g[i][j]表示i与j相邻时的代价
*/
```]