题目
问题描述
RJ有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
第一行为一个单独的整数 N 表示看过以后的小木柜的总数,其中 N<=60;
第二行为 N 个用空个隔开的正整数,表示 N 跟小木棍的长度。。
输出格式
输出文件仅一行,表示要求的原始木棍的最小可能长度。
样例输入
9 5 2 1 5 2 1 5 2 1
样例输出
6
样例输入
7
63 2 44 12 60 35 60
样例输出
276
分析
参考:https://blog.csdn.net/weixin_45485187/article/details/103490709
AC
看代码前看看链接中的解析非常有用!!!
在网上看到有的博主说有一个过滤被砍后的木棍的长度<=50的隐藏条件,但是本人测试后发现如果加上此条件在蓝桥的在线测评系统上无法通过,比如上文的第二个样例输入蓝桥给的答案是276,而加上此条件后答案是93,所以无法通过。也许是其他的在线测评系统有此限制,比如洛谷和C语言网。
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
/**
* 砍过以后的木棍的总数
*/
static int n = sc.nextInt();
/**
* 原始木棍的长度
*/
static int len;
/**
* 原始木棍长度为len时需要m根木棍
*/
static int m;
/**
* 所有砍过以后的木棍的长度和
*/
static int sum;
/**
* 标记len是否已经满足条件
*/
static boolean pd;
/**
* 标记砍过后的木棍是否已经用过 0表示没用过 1表示用过
*/
static int[] vis = new int[n + 1];
/**
* 存放砍过后木棍的长度
* 用Integer类型方便重写compare()函数实现数组由大到小排序
* 这里用n+2 是为了防止下标越界
*/
static Integer[] a = new Integer[n + 2];
public static void main(String[] args) {
// 初始化
Arrays.fill(a, 0);
for (int i = 1; i <= n; i++) {
a[i] = sc.nextInt();
sum += a[i];
}
// 重写compare()函数实现数组由大到小排序
Comparator<Integer> cmp = new Comparator<Integer>() {
@Override
public int compare(Integer i1, Integer i2) {
return i2 - i1;
}
};
Arrays.sort(a, 1, n + 1, cmp);
// 剪枝2,因为a[]已经排过序了,所以a[1]就是a[]中最大的一个
// 原始木棍的长度一定小于等于所有砍过后的木棍的长度的总和
for (len = a[1]; len <= sum; len++) {
// 剪枝1,原始木棍长度一定能够被所有砍过后的木棍的长度总和给整除,这样才能分成一些同样长的原始木棍
if (sum % len != 0) {
continue;
}
pd = false;
vis[1] = 1;
m = sum / len;
// 从第一根木棍开始拼,因为len一定大于等于a[i],所以可以直接把a[1]用来作为第一根木棍的第一截
hs(1, 1, len - a[1]);
if (pd) {
// 从小到大搜,第一个可行的len就是答案
System.out.println(len);
// 正确的话直接返回,不用再继续下去找匹配(后面的匹配只是在找另一组匹配而已)
return;
}
}
}
/**
* @param k 当前在拼第几根原始木棍
* @param last 在拼这根原始木棍时,用到的上一截砍过后的木棍的编号last,这一截就可以直接从last+1开始(因为在last之前的木棍已经用过了)
* @param length 要拼成第k根原始木棍还需要的长度
*/
private static void hs(int k, int last, int length) {
// 剪枝9,已经拼到最后一根原始木棍了,剩余的所有砍过后的木棍长度一定等于len,可以直接返回true
if (k == m) {
pd = true;
return;
}
// 这根原始木棍拼完了
if (length == 0) {
int i;
for (i = 1; i <= n; i++) {
// 剪枝4,找到一根还没有归位的砍过后的木棍
if (vis[i] == 0) {
break;
}
}
vis[i] = 1;
// 拼下一根原始木棍
hs(k + 1, i, len - a[i]);
// 这一步回溯很关键,因为如果不将这里回溯的话,就会出现 原始木棍k占了另一根原始木棍需要的砍过后的木棍 的情况
vis[i] = 0;
}
// 剪枝5和7,寻找可以拼入当前原始木棍的砍过后的木棍
for (int i = last + 1; i <= n; i++) {
// 第i根砍过后的木棍没有用过,而且拼进去不超出原始木棍还需要的木棍的长度
if (vis[i] == 0 && a[i] <= length) {
vis[i] = 1;
// 继续拼这根原始木棍
hs(k, i, length - a[i]);
// 回溯,说明此时需要换一根砍过后的木棍来拼这根原始木棍
vis[i] = 0;
// 这个优化尤其重要!发现当前的len是答案时就立马返回,不要再继续下去匹配了!
if (pd) {
return;
}
// 剪枝6,跳过和砍过后的木棍i长度相同的木棍
while (a[i].equals(a[i + 1])) {
i++;
}
}
}
}
}