子矩阵

洛咕

题意:给出如下定义:

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

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

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

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

\(n,m<=16.\)

分析:直接暴力枚举出所有的行的组成情况,然后用\(DP\)求出选哪些列能够使得分值最小.设\(f[i][j]\)表示从前i列中一共选了j列且选了第i列的最小分值,\(dis[i]\)表示第i列的分值(在已知选哪些行的情况下),\(dist[i][j]\)表示第i列与第j列产生的分值(在已知选哪些行的情况下),则\(f[i][j]=f[k][j-1]+dist[k][i]+dis[i].\)

在每次选完行之后,\(DP\)列之前,显然\(dis,dist\)两个数组都是可以预处理的.

时间复杂度应该是\(C_n^r*m^3\).反正能过.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=20;
int n,m,r,c,ans=1e9;
int bj[N],a[N],dis[N];
int jz[N][N],dist[N][N],f[N][N];
inline void dp(){
	memset(dis,0,sizeof(dis));
	memset(dist,0,sizeof(dist));
	memset(f,0x3f,sizeof(f));//初始化
	for(int j=1;j<=m;++j)
		for(int i=1;i<r;++i)
			dis[j]+=abs(jz[a[i]][j]-jz[a[i+1]][j]);
	for(int j=1;j<m;++j)
		for(int k=j+1;k<=m;++k)
			for(int i=1;i<=r;++i)
				dist[j][k]+=abs(jz[a[i]][j]-jz[a[i]][k]);	
	for(int i=1;i<=m;++i)f[i][1]=dis[i];
	for(int i=1;i<=m;++i)
		for(int j=2;j<=min(i,c);++j)
			for(int k=j-1;k<=i-1;++k)
				f[i][j]=min(f[i][j],f[k][j-1]+dist[k][i]+dis[i]);
	for(int i=c;i<=m;++i)ans=min(ans,f[i][c]);
}
inline void dfs(int now){//搜索枚举所有的行的组成情况
	if(now>r){dp();return;}//选完了之后就去dp列的情况
	for(int i=1;i<=n;++i)
		if(!bj[i]&&a[now-1]<i){//保证选出的行是递增的,也算是避免重复搜索状态
			bj[i]=1;
			a[now]=i;
			dfs(now+1);
			bj[i]=0;
		}
}
int main(){
	n=read();m=read();r=read();c=read();
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			jz[i][j]=read();
	dfs(1);printf("%d\n",ans);
    return 0;
}

posted on 2019-10-02 16:30  PPXppx  阅读(1740)  评论(0编辑  收藏  举报