JAVA实现矩阵与两倍子矩阵重量的差值最小
问题描述
给定一个矩阵 M,由 n 行 m 列组成,第 i 行第 j 列值为 M[i][j]。定义矩阵 M 的重量为矩阵中所有元素的和,几位weight(M)请找到矩阵左上角的一个子矩阵S(矩阵的前 r 行中的前 c 列组成),使得这个子矩阵的重量的两倍最接近矩阵 M 重量。即 |2 weight(S)-weight(M)| 最小。如果有多个子矩阵满足条件,请找出面积 r * c 最小的一个。如果仍然有多个子矩阵满足条件,请找出其中 r 最小的一个。
输入格式
输入第一行包含两个整数 n, m,表示矩阵的大小。
接下来 n 行,每行 m 个整数,表示给定的矩阵M。
输出格式
输出一行,包含两个整数 r, c,表示子矩阵为矩阵 M 的前 r 行中的前 c 列。
样例输入
3 4
3 0 1 1
1 0 1 1
1 1 -2 4
样例输出
2 3
数据规模和约定
对于 30% 的评测用例,1 <= n, m <= 20, -10 <= M[i][j] <= 10。
对于 50% 的评测用例,1 <= n, m <= 100, -100 <= M[i][j] <= 100。
对于所有评测用例,1 <= n, m <= 1000, -1000 <= M[i][j] <= 1000。
思路:获取矩阵左上角每个子矩阵的重量 * 2 - 矩阵的重量,再用Math工具类的abs方法取绝对值,记录下每次的差值和i,j(i*j为子矩阵的面积)当最小差值相等的时候在比较面积,保留面积小的子矩阵;而面积也相等时,其中取 r 最小的一个,因为是从上到下枚举的,r 是从小到大的,所以最小值相等,面积也相等的情况在后面出现的话 r 是一定大于 c 的,只需保留前面的结果就是题目所求。
用动态规划推导每个子矩阵的重量:
public class Main {
static int n,m,total=0;
static int[][] M;
static int r,c,d=Integer.MAX_VALUE;//r为长,c为宽,d为子矩阵面积
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
M = new int[n][m];
for(int i=0;i<n;i++){
for (int j = 0; j < m; j++) {
M[i][j]=sc.nextInt();
//矩阵的总重量
total+=M[i][j];
}
}
//将原数组扩大一圈,用来保存每个子矩阵的重量
int[][] dp=new int[n+1][m+1];
for(int i=1;i<=n;i++){
for (int j = 1; j <=m; j++) {
//状态转移方程,dp[i][j]所在的大的矩阵等于dp[i-1][j]上面的小矩阵加上dp[i][j-1]左边的小矩阵再减去dp[i-1][j-1]重复的小矩阵再加上M[i-1][j-1]的值
dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+M[i-1][j-1];
//检查是否符合
check(dp[i][j],i,j);
}
}
System.out.println(r+" "+c);
}
private static void check(int x, int i, int j) {
//因为是从上到下遍历的,所以差值最小,面积也相同时,r>c的
if(Math.abs(2*x-total)<d || Math.abs(2*x-total)==d && i*j<r*c){
r=i;
c=j;
d=Math.abs(2*x-total);
}
}
}
动态规划:后面的结果需要利用前面的结果推导出来,子问题的重叠