调整数组使得两个数组和的差最小

本文探讨了如何运用动态规划技巧解决两序列元素和差最小化的挑战,通过转换问题为背包问题并实施数据预处理,有效地降低了求解的复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

有两个序列 a,b,大小都为 n,序列元素的值任意整数,无序; 要求:通过交换 a,b 中的元素,使[序列 a 元素的和]与[序列 b 元素的和]之间的差最小。

Input:

输入第一行为用例个数, 每个测试用例输入为两行,分别为两个数组,每个值用空格隔开。

Output:

       输出变化之后的两个数组内元素和的差绝对值。

Sample Input 1 :

1

100 99 98 1 2 3

1 2 3 4 5 40

Sample Output 1:

48


使用动态规划求解:

原问题转换为:有2n个非负整数(题目给的条件是值任意,后面我会介绍转换方法),和为 S_{2n},要从这2n个数中选出n个数,使得这n个数的和S_{n}满足:

                                                  \min(\frac{S_{2n}}{2} - S_{n})
​描述为背包问题为:

从2n个物品中,选出n个物品,放入容量为\frac{S_{2n}}{2}的背包中,使得背包装的东西尽可能的多。

这其实在原来的01背包问题上,多加了一个限制,即放入的物品数量为n个的限制。

我们可以在原来的问题的基础上多加一维来满足对放入物品数量的限制。

状态转移方程为:

设dp[i][j][v]为从前n个物品中,选出j个物品(j<=i),放入容量为v的背包中,所能获得的最大价值。a[i]表示第i个物品的价值。

                                           dp[i][j][v] = \max \left (dp[i-1][j][v],dp[i-1][j-1][v-a[i]]+a[i] \right )

空间优化后:

                                               dp[j][v] = \max \left ( dp[j][v],dp[j-1][v-a[i]]+a[i] \right )

数据预处理:

数组中的元素满足平移不变性,即数组中的所有元素加上或减去同一个数,两数组的和的差不变。

好处1:因为本数组中的元素有正有负,对数组中的每个元素,减去数组中的最小值,可以将数组中的所有元素转换为非负元素,进而可以转换为背包问题求解。

好处2:如果数组中的数本来就全为正数,则所有数减去最小值,有效减小背包的容量,降低求解的空间复杂度。

参考代码(java):

import java.util.Scanner;


public class test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		int cases = Integer.parseInt(sc.nextLine());
		for(int i = 0; i < cases ; i++) {
			String str1 = sc.nextLine();
			String str2 = sc.nextLine();
			String[] str3 = str1.split(" ");
			String[] str4 = str2.split(" ");
			int n = str3.length;
			int[] arr = new int[2 * n];
			int sum = 0;
			int min = Integer.MAX_VALUE;
			for(int j = 0; j < n; j++){
				arr[j] = Integer.parseInt(str3[j]);
				arr[j+n] = Integer.parseInt(str4[j]);
				if(arr[j] < min){
					min = arr[j];
				}
				if(arr[j+n] < min){
					min = arr[j+n];
				}
			}
			sum = 0;
			for (int j = 0; j < arr.length; j++) {
				arr[j] = arr[j] - min;
				sum += arr[j];
			}
			int vmax = sum / 2;
			int dp[][] = new int[n+2][vmax+2];
			int sel[][] = new int[n+2][vmax+2];
			for (int j = 0; j < dp.length; j++) {
				for (int k = 0; k < dp[j].length; k++) {
					if(j == 0) {
						dp[j][k] = 0;
					}else {
						dp[j][k] = -1;
					}
					sel[j][k] = 0;
				}
			}
			//i从1开始而不是从0,是为了避免j-1<0越数组下界的情况,对arr数组进行遍历
			for (int j = 1; j <= 2*n; j++) {
                                    //注释1
				for (int k = (j > n ? n : j); k >= 1  ; k--) {
					
					for (int v = arr[j-1]; v <= vmax; v++) {
						//注释2
						if(dp[k-1][v-arr[j-1]] < 0) {
							continue;
						}else{
							dp[k][v] = Math.max(dp[k][v], dp[k-1][v-arr[j-1]] + arr[j-1]);
						}
						
					}
				}
			}
			System.out.println(sum - 2*dp[n][vmax]);
		}
			
	}

}

注释1:因为dp数组去掉了一维,且物品被拿出后不能放回,所以要进行倒序遍历,避免重复选取
             如果正序遍历,当遍历到第二个元素时候,假设arr[2] = 10, vmax = 100,可以将其作为第一个元素选取,即

          dp[1][10]=10,dp[1][10~100]=10,也可以将该元素作为选取到的第二个元素,这种情况下,数组中需要有已经选到的第一               个元素,而存在dp[1][10~100] = 10,因为dp[1][10] + 10 > dp[1][20](即10),所以可以将arr[2]作为第二个选取的元素,在                 这种情况下,arr[2]被选了两次,既作为被选得第一个元素,又作为被选得第二个元素。

             倒序遍历的时候,该元素永远是目前最后一个选取的元素,不会存在该元素在之前被选过的情况,避免了重复选取

注释2:当前元素为选出的第k个元素,若dp[k-1][v-arr[j-1]<0,即在选出该元素之前,之前的背包状态中未选择过元素,背包为空,背包中不存在选好的前k-1个元素,增加背包内物品的容量,继续下次循环,直到找到一个对应的背包容量v,在该容量中,选好了k-1个物品,之后在进行判断,决定该物品是否放入包中。(个人理解,请大佬指正)

PS:还是自己太菜,想了一个半小时多,才完全想通如何实现的降维。

如有错误,请您指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值