今天看了看枚举与搜索的题,意外中看到了一道“看似简单”的普及组题,然而事实是我想了好久,然后终于搞出来了。
题目:
题目描述
给出如下定义:
子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第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列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
(本题目为2014NOIP普及T4)
输入输出格式
输入格式:
第一行包含用空格隔开的四个整数
n,m,r,c,
意义如问题描述中所述,每两个整数之间用一个空格隔开。
接下来的n行,每行包含m个用空格隔开的整数,用来表示问题描述中那个n行m列的矩阵。
输出格式:
输出共1行,包含1个整数,表示满足题目描述的子矩阵的最小分值。
输入输出样例
输入样例#1:
5 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
输出样例#1:
6
输入样例#2:
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
输出样例#2:
说明
【输入输出样例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≤a[i][j]≤20 ;
对于100%的数据, 1≤n≤16,1≤m≤16 ,矩阵中的每个元素 1≤a[i][j]≤1,000 ,
1≤r≤n,1≤c≤m 。
分析:
最暴力的算法是枚举选择哪些行、列。复杂度为
O(C(n,r)×C(m,c))
。不过显然不能承受。(C为组合数)
注意到虽然
O(C(n,r)×C(m,c))
不能承受,但
O(C(n,r))
或
O(C(m,c))
是可以接受的。
不妨考虑枚举其中一个(假设枚举行)。
枚举完行后,由于行已确定,因此可以把所有行捆绑,视为一个整体。
处理处列与列之间的价值,然后可以用动态规划解决这个问题。
设
dp[i][k]
表示前i列选了k列,并且第i列强制被选。那么转移方程为:
dp[i][k]=dp[j][k−1]+cost[j][i]+val[i]
,其中
j<i,cost[j][i]
表示第i列与第j列相邻的花费,
val[i]
表示第i列内的花费。
答案即为
mindp[i][c]
。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
const int inf = 1e9 + 7;
int n, m, r, c;
int ans = inf;
int a[17][17], C[17], f[17][17], cost[17][17], val[17];
inline int read(){
int r = 0, z = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') z = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){r = r * 10 + ch - '0'; ch = getchar();}
return r * z;
}
int Max(int a, int b){return a > b ? a : b;}
int Min(int a, int b){return a < b ? a : b;}
int DP(){
int res = inf;
for(int i = 1; i <= m; i ++){
val[i] = 0;
for(int j = 1; j < r; j ++)
val[i] += abs(a[C[j]][i] - a[C[j + 1]][i]);
}
memset(cost, 0, sizeof(cost));
for(int i = 1; i <= m; i ++)
for(int j = i + 1; j <= m; j ++){
cost[i][j] = 0;
for(int k = 1; k <= r; k ++)
cost[i][j] += abs(a[C[k]][i] - a[C[k]][j]);
}
for(int i = 0; i <= m; i ++) f[i][0] = 0;
for(int i = 1; i <= m; i ++){
for(int k = 1; k <= Min(i, c); k ++){
f[i][k] = inf;
for(int j = k - 1; j < i; j ++)
f[i][k] = Min(f[i][k], f[j][k - 1] + cost[j][i] + val[i]);
}
}
for(int i = c; i <= m; i ++) res = Min(res, f[i][c]);
return res;
}
void dfs(int u, int cnt){
if(u > n){
//cout<<1<<endl;
if(cnt == r) ans = Min(ans, DP());
return ;
}
dfs(u + 1, cnt);
C[cnt + 1] = u;
dfs(u + 1, cnt + 1);
}
void init(){
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, 0);
printf("%d\n", ans);
}
int main(){
init();
return 0;
}