题目描述
给出如下定义:
- 子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第2、4行和第2、4、5列交叉位置的元素得到一个2×3的子矩阵如右图所示。
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
其中一个2×3的子矩阵是
4 7 4
8 6 9
-
相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
-
矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个n行m列的正整数矩阵,请你从这个矩阵中选出一个r行c列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
输入输出格式
输入格式:
第一行包含用空格隔开的四个整数n,m,r,c,意义如问题描述中所述,每两个整数之间用一个空格隔开。
接下来的n行,每行包含m个用空格隔开的整数,用来表示问题描述中那个n行m列的矩阵。
输出格式:
一个整数,表示满足题目描述的子矩阵的最小分值。
输入输出样例
7 7 3 3 7 7 7 6 2 10 5 5 8 8 2 1 6 2 2 9 5 5 6 1 7 7 9 3 6 1 7 8 1 9 1 4 7 8 8 10 5 9 1 1 8 10 1 3 1 5 4 8 6
16
说明
【输入输出样例1说明】
该矩阵中分值最小的2行3列的子矩阵由原矩阵的第4行、第5行与第1列、第3列、第4列交叉位置的元素组成,
为 6 5 6
7 5 6
其分值为:
|6−5| + |5−6| + |7−5| + |5−6| + |6−7| + |5−5| + |6−6| =6
【输入输出样例2说明】
该矩阵中分值最小的3行3列的子矩阵由原矩阵的第4行、第5行、第6行与第2列、第6列、第7列交叉位置的元素组成,选取的分值最小的子矩阵为
9 7 8
9 8 8
5 8 10
【数据说明】
对于50%的数据,1 ≤ n ≤ 12,1 ≤ m ≤ 12,矩阵中的每个元素1 ≤ aij ≤ 20;
对于100%的数据,1 ≤ n ≤ 16,1 ≤ m ≤ 16,矩阵中的每个元素1 ≤ aij ≤ 1,000,1 ≤ r ≤ n,1 ≤ c ≤ m。
题解
解析
本题目为2014NOIP普及T4,应该是一道很有名的题目,洛谷上的难度是提高+/省选-。其实,如果想通了,这题真的不是很难。
本题的题意就是在一个n行m列的正整数矩阵中,选出一个r行c列的矩阵,使得该矩阵中每一对相邻元素之差的绝对值之和最小。
首先,选行的话可以用DFS排列出所有r行的情况(可以用回溯)
接着,可以进行预处理,对所选的r行而言,处理出它所有元素的“分值”;
对于上下两行元素之间的差,要一维数组(因为DFS选行时,就相当于有一个数组了);
对于相邻两列的处理,必选选用二维数组(需要存储行的个数和列的个数);
或许这样讲不太清楚,那么可以这么理解:
1.设v[i]表示所选出的相邻两行第i个元素的差的绝对值
2.设w[i][j]表示所选出的第i-1行和第i行的第j列上所有元素的差的绝对之和
然后,就可以动规了,本题就转化为在m列中选出c列,满足最小即可(有点像背包),但是要注意边界问题:
1.第一层循环毫无疑义是i从1~m枚举,注意第二层循环就是j从1~min(i,c),因为只要选c列
2.当j等于1时,f[i][j]=v[i],这应该不需要解释
3.当j等于i时,即前i列都取,那么f[i-1][j-1]+v[i]+w[i][j-1];
把算法再整理一遍
1.通过DFS求出在n行中取出r行的所有情况
2.预处理出行差和列差
3.通过动规(类似背包问题)计算出最优值
代码
1 #include <set> 2 #include <map> 3 #include <queue> 4 #include <stack> 5 #include <ctime> 6 #include <cmath> 7 #include <cstdio> 8 #include <cstring> 9 #include <cstdlib> 10 #include <iostream> 11 #include <algorithm> 12 13 using namespace std; 14 15 const int N=20; 16 const int INF=2147483647; 17 18 int ans=INF; 19 20 int n,m,r,c; 21 int a[N][N]; 22 23 int tmp=1; 24 int h[N]={0};//hang 25 int w[N][N]={0},v[N]={0}; 26 int f[N][N]; 27 28 void before_work() 29 { 30 memset (w,0,sizeof (w)); 31 memset (v,0,sizeof (v)); 32 for (int i=1;i<=m;i++) 33 for (int j=1;j<r;j++) 34 v[i]=abs(a[h[j]][i]-a[h[j+1]][i]); 35 for (int i=2;i<=m;i++) 36 for (int j=1;j<i;j++) 37 for (int k=1;k<=r;k++) 38 w[i][j]+=abs(a[h[k]][i]-a[h[k]][j]); 39 return; 40 } 41 42 void DP() 43 { 44 for (int i=1;i<=m;i++) 45 { 46 for (int j=1;j<=min(i,c);j++) 47 { 48 if (j==1)//边界1 49 f[i][j]=v[i]; 50 else if (i==j)//边界2 51 f[i][j]=f[i-1][j-1]+v[i]+w[i][j-1]; 52 else 53 { 54 f[i][j]=INF; 55 for (int k=j-1;k<i;k++) 56 f[i][j]=min(f[i][j],f[k][j-1]+v[i]+w[i][k]); 57 } 58 if (j==c)ans=min(ans,f[i][c]);//求答案 59 } 60 } 61 } 62 63 void DFS(int cnt) 64 { 65 if (cnt>n) 66 { 67 before_work();//预处理 68 DP();//动规 69 return; 70 } 71 if (r-tmp+1==n-cnt+1) 72 {//一个小优化。如果cnt和cnt以后的元素必须全部取完,才能选出r个,那么cnt必须取,否则就取不到r个元素。 73 h[tmp++]=cnt; 74 DFS(cnt+1); 75 h[tmp--]=0; 76 return; 77 } 78 DFS(cnt+1);//这一行不取 79 if (tmp<=r) 80 {//这一行取 81 h[tmp++]=cnt; 82 DFS(cnt+1); 83 h[tmp--]=0; 84 } 85 } 86 87 void work() 88 { 89 scanf ("%d%d%d%d",&n,&m,&r,&c); 90 for (int i=1;i<=n;i++) 91 for (int j=1;j<=m;j++) 92 scanf ("%d",&a[i][j]); 93 DFS(1); 94 printf ("%d ",ans); 95 return; 96 97 } 98 99 int main() 100 { 101 work(); 102 return 0; 103 }
出处:https://www.cnblogs.com/yujustin/