乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
输入格式
输入包含多组数据,每组数据包括两行。
第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。
第二行是截断以后,所得到的各节木棍的长度。
在最后一组数据之后,是一个零。
输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
数据范围
数据保证每一节木棍的长度均不大于 50。
输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5
解题代码:
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int N = 70;
static int[] a = new int[N];
static boolean[] st = new boolean[N];
static int sum;//记录木棍的总长度
static int len;
static int n;
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
while(true){
//读取输入数据
n = scanner.nextInt();
if(n == 0) break;
sum = 0;
len = 0;
for(int i = 0; i < n; i++){
a[i] = scanner.nextInt();
sum += a[i];
len = Math.max(len,a[i]);//找出被截断的木棍中最大的长度
}
//剪枝1
//对数组进行降序排列
Arrays.sort(a,0,n);
for(int i = 0, j = n - 1; i < j; i++, j--){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
Arrays.fill(st,0,n,false);//表示每根木棍都没有被用过
while (true){
if(sum % len == 0 && dfs(0,0,0)){
System.out.println(len);
break;
}
len++;
}
}
}
static boolean dfs(int u, int cur, int start) {
//u表示当前枚举到第几个长木棍
//cur表示当前正在拼的长木棍的长度
//start表示当前的小木棍的下标是多少
if (u * len == sum) return true;//如果已经把当前小木棍都拼成长木棍的话,返回true;
if (cur == len) return dfs(u + 1, 0, 0);//表示当前长木棍已经拼好了,要开始拼下一个长木棍
for (int i = start; i < n; i++) {
//剪枝2
if (!st[i] && cur + a[i] <= len && (i == 0 || a[i] != a[i - 1] || st[i - 1])) {
st[i] = true;
if (dfs(u, cur + a[i], i + 1)) return true;
st[i] = false;
//剪枝3
if (cur == 0 || cur + a[i] == len) return false;
}
}
return false;
}
}
解题思路:
因为是一堆等长的木棍,所以可以确定每根木棍的长度会在被截断的木棍中最大值与木棍总长度之间,在这个范围内进行枚举即可。做出的剪枝优化有:1.对数组进行降序排列,找到符合的len可以直接输出。2.对于相同长度的木棍:在遍历未使用的木棍时,如果当前木棍的长度与前一个木棍的长度相同,并且前一个木棍未被使用过,则跳过当前木棍,因为它们可以产生相同的拼接方案。3.在遍历未使用的木棍时,如果当前木棍的长度为 0,或者将当前木棍拼接到当前长木棍上后达到了目标长度,则直接返回 false。
对于dfs函数中的if条件语句解读:
1. `if (u * len == sum)`:这个条件语句用于判断当前拼接的所有木棍的长度是否等于总长度的整数倍,如果是,则表示所有木棍都已经成功拼接成长木棍,函数返回 `true`。
2. `if (cur == len)`:这个条件语句用于判断当前拼接的木棍长度是否等于目标长度 `len`,如果是,则表示当前木棍已经拼接完成,需要开始尝试拼接下一个长木棍。
3. `if (!st[i] && cur + a[i] <= len && (i == 0 || a[i] != a[i - 1] || st[i - 1]))`:这个条件语句用于判断当前尝试拼接的木棍是否符合要求,其中:
- `!st[i]`:确保当前木棍没有被使用过。
- `cur + a[i] <= len`:确保将当前木棍拼接到当前长木棍上后不超过目标长度 `len`。
- `(i == 0 || a[i] != a[i - 1] || st[i - 1])`:这部分条件用于剪枝,确保相同长度的木棍只会被使用一次。如果当前木棍的长度与前一个木棍相同(`a[i] == a[i - 1]`),则当前木棍只有在前一个木棍已经被使用过时才可以使用,否则跳过当前木棍。
4. `if (cur == 0 || cur + a[i] == len)`:这个条件语句同样用于剪枝,确保当前木棍的长度为 0 或者当前木棍的长度加上某根木棍的长度等于目标长度时,返回 `false`。这个条件语句在遇到这种情况时,会跳过当前木棍的尝试,直接返回 `false`,以提高搜索效率。