c++的实现方式可以参考http://blog.csdn.net/lyy289065406/article/details/6647960
解题思路:
DFS+剪枝
POJ2362的强化版,重点在于剪枝
令InitLen为所求的最短原始棒长,maxlen为给定的棒子堆中最长的棒子,sumlen为这堆棒子的长度之和,那么InitLen必定在范围[maxlen,sumlen]中
根据棒子的灵活度(棒子越长,灵活度越低) DFS前先对所有棒子降序排序
剪枝:
1、 由于所有原始棒子等长,那么必有sumlen%Initlen==0;
2、 若能在[maxlen,sumlen-InitLen]找到最短的InitLen,该InitLen必也是[maxlen,sumlen]的最短;若不能在[maxlen,sumlen-InitLen]找到最短的InitLen,则必有InitLen=sumlen;
3、 由于所有棒子已降序排序,在DFS时,若某根棒子不合适,则跳过其后面所有与它等长的棒子;
4、 最重要的剪枝:对于某个目标InitLen,在每次构建新的长度为InitLen的原始棒时,检查新棒的第一根stick[i],若在搜索完所有stick[]后都无法组合,则说明stick[i]无法在当前组合方式下组合,不用往下搜索(往下搜索会令stick[i]被舍弃),直接返回上一层
import java.io.BufferedReader;
import java.io.InputStreamReader;import java.util.Arrays;
import java.util.StringTokenizer;
public class Main {
static boolean[] used;//用于记录组合时木棒的使用情况
static int len;//截断后,木棒的根数
static int[] s;//用来存储截断后木棒的长度
static int sum;//所有木棒的总长度
static int max;//木棒的最大长度
static int parts;//理论上某种组合方式的木棒的根数
public static void main(String[] args) throws Exception {
BufferedReader read = new BufferedReader(new InputStreamReader(
System.in));
//判断输入的木棒的根数是否为0.
while ((len = Integer.parseInt(read.readLine()) ) != 0) {
//根据截断后木棒的根数来创建一个整型数组
s = new int[len];
StringTokenizer take = new StringTokenizer(read.readLine());
int index = 0;
sum = 0;
used = new boolean[len];
//hasMoreTokens() .测试此 tokenizer 的字符串中是否还有更多的可用标记。
while (take.hasMoreTokens()) {
//nextToken().返回此 string tokenizer 的下一个标记。
//将每一根木棒的长度存入s[]数组中
s[index] = Integer.parseInt(take.nextToken());
//计算木棒的总长度
sum += s[index++];
}
//sort(int[] a) .对指定的 int 型数组按数字升序进行排序。
Arrays.sort(s);
//获得截断后木棒的最大长度
max = s[len - 1];
/**
*
*/
//在[max,sum]之间寻找initlen
for (; max <= sum; max++) {
//如果总长度sum对max整除
if (sum % max == 0) {
//计算以max为原始长度,这时候应该有多少根木棒
parts = sum / max;
if (search(0, len - 1, 0)) {
System.out.println(max);
break;
}
}
}
}
}
/**
* search(int res, int next, int cpl)主要用于寻找最短的原始棒长
* 其主要思想包括以下几点:
* 1)首先判断当前的组合长度res是否已经满足当前的原始棒长的可能值max,
* 如果满足了,将组合长度res置为0,next置为len-2,已组合的木棒根数cpl加1.
*
* 2)判断已组合的木棒根数cpl是否和理论上要组合木棒根数parts一致,
* 如果一致,这证明已经找到最短原始长度,返回true
*
* 3)如果进入到这一步,则证明还没有组合完成,这是重新开始组合一根木棒
* 1))首先判断是否还有棒子用于组合,如果没有,则执行接下来的false.
* 如果有,判断该棒子是否已经被使用过,如果被使用过,则使用下一根棒子,
* 如果没有被使用过,则判断组合长度res+该棒子的长度s[next]是否<=当前最短原始棒长的可能值max
* 如果不成立,则证明将剩下的棒子的长度都加起来,如果······
*
*
*
* @param res
* @param next
* @param cpl
* @return
*/
public static boolean search(int res, int next, int cpl) {
/**
* res:已经组合的长度
* next:要组合的木棒
* cpl:已经组合的木棒的根数
*/
/**
* 如果组合的长度已经达到木棒的最大长度
* 则进行下一轮组合:
* 组合长度res置0
* 已经组合好的木棒根数cpl的值+1
* 确定下一根要组合的木棒
*/
if (res == max) {
res = 0;
next = len - 2;
cpl++;
}
/**
* 如果已经组合好巅峰木棒数与理论上的相等,则
* 返回true
*/
if (cpl == parts) {
return true;
}
//从大到小开始组合
while (next >= 0){
//如果next对应的木棒还没有使用
if (used[next] == false) {
//如果当前组合长度小于木棒的最大长度
if (res + s[next] <= max){
//将当前木棒的使用属性标识为true
used[next] = true;
/**
* 继续组合,如果下一轮组合刚刚好满足条件,则
* 返回true,组合完成
*/
if (search(res + s[next], next - 1, cpl)) {
return true;
}
/**
* 如果上面的if语句没有执行则证明,还没有组合完成
*
*/
/**
* 如果执行到这里,则证明,当前木棒并不适合用于组合
* 将当前已经使用到的木棒的使用属性记为false
*/
used[next] = false;
/**
* 对于新棒的第一根棒子如果在搜索万所搜棒子以后都无法组合
* ,则证明该棒子在当前组合方式下无法组合
*所以,直接返回上一层
*/
if (res == 0) {
break;
}
if (res + s[next] == max) {
break;
}
}
/**
*如果当前组合长度加上接下来的那一根木棒的长度大于最大长度
*,或者 已经完成这一次的组合,却还没有达到最大最大长度
*/
int i = next - 1;
/**
* while循环主要体现剪枝法:
* 如果当前木棒的长度不合适,那么
* 接下来的和当前木棒长度相同的木棒也不合适
*
*/
while (i >= 0 && s[i] == s[next]) {
i--;
}
next = i;
/**
* for循环的主要逻辑:
* 遍历接下来的剩下的所有木棒,
* 计算剩下所有木棒的总长度l_s
*/
int l_s = 0;
for (int j = next; j >= 0; j--) {
if (!used[j]) {
l_s += s[j];
}
}
/**
* if循环的主要逻辑:
* 如果实际剩下所有木棒的总长度l_s<
* 还需要的木棒的长度,则,跳出循环
* 证明
*/
if (l_s < max - res) {
break;
}
/**
* 剩下的木棒的总长度>还需要的木棒的长度,则
* 继续组装
*/
continue;
}
//如果当前的木棒已经被使用过,将下标 - 1
next--;
}
//如果遍历完所有的木棒都没有组合成功,则
//返回false.
return false;
}
}