0004 ALGO1004-蓝桥杯-无聊的逗

问题描述

试题 算法训练 无聊的逗
image

原始解法

最大的平衡序列只能是总长度的 \(\frac{1}{2}\),使用两次 dfs 搜索希望得到的结果。

import java.util.Arrays;
import java.util.Scanner;

/**
 * @author HuaWang135608
 * @date 2023.03.13 11:18:11
 * @description [试题 算法训练 无聊的逗](http://lx.lanqiao.cn/problem.page?gpid=T2992)
 */

public class A1004_BoringDou {

	// 解题思路
	// 对序列求和(sum)
	// 从 sum / 2 往下搜索
	// 做两次 dfs 获得 sum / 2 的序列
	public static void main(String[] args) {
		try (Scanner sc = new Scanner(System.in)) {
			// 数据输入
			int src_n = sc.nextInt();
			int[] src = new int[src_n];
			for (int i=0; i<src.length; ++i) {
				src[i] = sc.nextInt();
			}
			long res = 0;
			
			// 数据处理
			long sum = totalLength(src); // 获取序列总长度
			boolean[] available = new boolean[src_n];
			for (long s=sum>>1; s>0; --s) { // 序列长度和的一半是可能的最大结果
				Arrays.fill(available, true); // 初始化原始矩阵标识,表示数据均可用
				if (dfs(src, available, 0, s)) { // 第一次搜索到和为 s 的序列
					if (dfs(src, available, 0, s)) { // 再次搜索到和为 s 的序列
						res = s;
						break;
					}
				}
			}
			
			// 结果输出
			System.out.println(res);
		}
	}
	
	/**
	 * 获得棍子的总长度
	 * @param src 需要求和的序列
	 * @return 序列的和
	 */
	public static long totalLength(int[] src) {
		long sum = 0;
		for (int val : src) {
			sum += val;
		}
		return sum;
	}
	
	/**
	 * 使用 dfs 搜索序列中可能得到 sum 的结果
	 * @param alignment 原始数据序列
	 * @param available 标识对应数据是否被使用
	 * @param currentSum 当前求得的和
	 * @param sum 需要得到的和
	 * @return 是否可以求得 sum
	 */
	public static boolean dfs(int[] alignment, boolean[] available, long currentSum, long sum) {
		if (currentSum == sum) { // 求得结果,则返回 true
			return true;
		} else if (currentSum > sum) { // 当前和已经大于需要的结果,则更换数据
			return false;
		}
		for (int i=0; i<alignment.length; ++i) {
			if (available[i]) {
				available[i] = false;
				if (dfs(alignment, available, currentSum + alignment[i], sum)) {
					return true;
				}
				available[i] = true;
			}
		}
		return false;
	}
	
}

结果只有 70 分,原因是 dfs 第一次搜索到合适的结果,可能会妨碍第二次的结果,如:
15
1 1 10 10 3 3 19 19 16 16 19 19 19 19 15
此输入的最大结果为 94,但是只找到了 89;

修正解法

依靠状态穷举的方式,构建两个平衡堆,则每个棍子有三种状态:进入小堆、进入大堆和丢弃;可以将棍子都放入一个堆中作为大堆,将大堆的棍子拿到小堆、置于大堆或丢弃。
参考:蓝桥杯 试题 算法训练 无聊的逗 C++ 详解

import java.util.Scanner;

/**
 * @author HuaWang135608
 * @date 2023.03.13 11:18:11
 * @description [试题 算法训练 无聊的逗](http://lx.lanqiao.cn/problem.page?gpid=T2992)
 */

public class A1004_BoringDou {

	// 解题思路
	// 1. 对序列求和(sum)
	// 2. 穷举棍子的所有状态,得出最长的结果
	// 		小堆选,大堆减;小堆不选,大堆不减;小堆不选,大堆不选
	public static void main(String[] args) {
		try (Scanner sc = new Scanner(System.in)) {
			// 数据输入
			// 输入棍子数量
			int src_n = sc.nextInt();
			// 输入棍子长度
			int[] src = new int[src_n];
			for (int i=0; i<src.length; ++i) {
				src[i] = sc.nextInt();
			}
			long sum1 = 0; // 小堆棍子长度
			long sum2 = 0; // 大堆棍子长度
			long res = 0; // 最终结果
		
			// 数据处理
			for (int val : src) { // 获得棍子总长度
				sum2 += val; // 将所有棍子放到大堆里
			}
			// 穷举所有状态
			res = exhaustivity(src, 0, sum1, sum1, sum2, src_n);
			
			// 结果输出
			System.out.println(res);
		}
	}
	
	public static long exhaustivity(int[] res, int index, long max
			, long sum1, long sum2, int length) {
		if (sum1 == sum2) { // 更新结果后返回
			return max>sum1 ? max : sum1;
		}
		if (sum1>sum2 || index>=length) {
			return max; // 小堆数据大于大堆 或 下标越界,则返回最大的结果
		} else {
			// 小堆选,大堆减
			long max1 = exhaustivity(res, index + 1, max
					, sum1 + res[index], sum2 - res[index], length);
			max = max>max1 ? max : max1; // 更新结果
			// 小堆不选,大堆不动
			max1 = exhaustivity(res, index + 1, max, sum1, sum2, length);
			max = max>max1 ? max : max1; // 更新结果
			// 小堆不选、大堆减
			max1 = exhaustivity(res, index + 1, max
					, sum1, sum2 - res[index], length);
			max = max>max1 ? max : max1; // 更新结果
			return max;
		}
	}
	
}

问题

依据题意,将棍子按长度排序后,若有偶数个长棍子,则不需要再次穷举,直至第一个无法配对的长棍子为止。但是测试只有 80 分,希望有佬可以修正。

import java.util.Arrays;
import java.util.Scanner;

/**
 * @author HuaWang135608
 * @date 2023.03.13 11:18:11
 * @description [试题 算法训练 无聊的逗](http://lx.lanqiao.cn/problem.page?gpid=T2992)
 */

public class A1004_BoringDou {

	// 解题思路
	// 1. 对序列求和(sum)
	// 2. 给两个序列的长度大小,初始状态为 0 和 sum,表示未分配
	// 3. 对序列进行升序排序,取出偶数个长度一致的棍子
	// 4. 遇到第一个奇数个长度的棍子则需要配合其它长度的棍子进行组合
	// 5. 穷举剩下棍子的所有状态,得出最长的结果
	// 		小堆选,大堆减;小堆不选,大堆不减;小堆不选,大堆不选
	public static void main(String[] args) {
		try (Scanner sc = new Scanner(System.in)) {
			// 数据输入
			// 输入棍子数量
			int src_n = sc.nextInt();
			// 输入棍子长度
			int[] src = new int[src_n];
			for (int i=0; i<src.length; ++i) {
				src[i] = sc.nextInt();
			}
			long sum1 = 0; // 小堆棍子长度
			long sum2 = 0; // 大堆棍子长度
			long res = 0; // 最终结果
		
			// 数据处理
			for (int val : src) { // 获得棍子总长度
				sum2 += val; // 将所有棍子放到大堆里
			}
			Arrays.sort(src); // 升序排序
			// 降低复杂度(可能)
			// 从尾部取出偶数个重复长度的棍子(至第一个无法配对的长度为止)
			for (int i=src.length-1; i>=1; i-=2) {
				if (src[i] == src[i - 1]) {
					res += src[i]; // 加上一个重复长度的棍子
					sum2 -= src[i]<<1; // 减去这两个重复长度的棍子
					src_n -= 2; // 要处理的棍子数量减 2
				} else {
					break;
				}
			}
			
			// 穷举剩下棍子的所有状态:在小堆,在大堆,丢弃
			// 待处理的棍子,待处理的棍子下标,可以得到的最大平衡长度,小堆长度,大堆长度,待处理的棍子数量
			res += exhaustivity(src, 0, sum1, sum1, sum2, src_n);
			
			// 结果输出
			System.out.println(res);
		}
	}
	
	public static long exhaustivity(int[] res, int index, long max
			, long sum1, long sum2, int length) {
		if (sum1 == sum2) { // 更新结果后返回
			return max>sum1 ? max : sum1;
		}
		if (sum1>sum2 || index>=length) {
			return max; // 小堆数据大于大堆 或 下标越界,则返回最大的结果
		} else {
			// 大堆拿到小堆(在小堆)
			long max1 = exhaustivity(res, index + 1, max
					, sum1 + res[index], sum2 - res[index], length);
			max = max>max1 ? max : max1; // 更新结果
			// 保持原状态(在大堆)
			max1 = exhaustivity(res, index + 1, max, sum1, sum2, length);
			max = max>max1 ? max : max1; // 更新结果
			// 丢弃
			max1 = exhaustivity(res, index + 1, max
					, sum1, sum2 - res[index], length);
			max = max>max1 ? max : max1; // 更新结果
			return max;
		}
	}
	
}
posted @ 2023-03-13 17:15  华王135608  阅读(144)  评论(0)    收藏  举报