0004 ALGO1004-蓝桥杯-无聊的逗
问题描述
原始解法
最大的平衡序列只能是总长度的 \(\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;
}
}
}


浙公网安备 33010602011771号