POJ 1011 Sticks 经典的dfs+剪枝

7 篇文章 0 订阅

题目点我
非常经典的搜索题目。
题目描述:乔治拿来一组等长的棍子,将它们随机地裁断(截断后的小段称为木棒),使得每一节木棒的长度都不超过50个长度单位。然后他又想把这些木棒恢复到为裁截前的状态,但忘记了棍子的初始长度。
请你设计一个程序,帮助乔治计算棍子的可能最小长度。每一节木棒的长度都用大于零的整数表示。

基本思路是从木棒中最长长度 max{Li} 到总长度的一半 totalLen2 枚举棍子的长度 L ,并且保证totalLen L 的整数倍,用深度优先搜索来判断是否能够用现有木棒拼成若干L长度的棍子。但是其中涉及到剪枝的问题,即如何尽量早的判断出一根拼好的棍子需要拆掉,避免徒劳的尝试。

剪枝1:每次开始拼第i根棍子的时候,必定选剩下的木棒里最长的一根,作为该棍子的第一根木棒。
即:就算由于以后的拼接失败,需要重新调整第i根棍子的拚法,也不会考虑替换第i根棍子中的第一根木棒(换了也没用)。如果在此情况下怎么都无法成功,那么就要推翻第i-1根棍子的拚法。如果不存在第i-1根棍子,那么就推翻本次假设的棍子长度,尝试下一个长度
因为假设替换后能全部拼成功,那么这被换下来的第一根木棒,必然会出现在以后拼好的某根棍子k中。那么我们原先拼第i根棍子时, 就可以用和棍子k同样的构成法来拼,照这种构成法拼好第i根棍子,继续下去最终也应该能够全部拼成功。

剪枝2:不要希望通过仅仅替换已拼好棍子的最后一根木棒就能够改变失败的局面。
假如一根棍子拼法是这样的:

123

如果后面的棍子拼接无法成功,导致这根棍子要被拆掉,那么只拆第3根,试图用更短的木棒来填满这个空是徒劳的。起码要回溯到上一层,即第2根木棒也要换掉。
因为 假设替换3后最终能够成功,那么3必然出现在后面的某个棍子k里。将棍子k中的3和棍子i中用来替换3的几根木棒对调,结果当然一样是成功的。这就和i原来的拚法会导致不成功矛盾。
3

12pmn3

剪枝3:如果某次拼接选择长度为 L1 的木棒,导致最终失败,则在同一位置尝试下一根木棒时,要跳过所有长度为 L1 的木棒

剪枝4:拼每一根棍子的时候,应该确保已经拼好的部分,长度是从长到短排列的,即拼的过程中要排除类似下面这种情况:
未完成的棍子i:

123

走到这一步,肯定后面不成功。如果这样能拼成功的话,2、3互换肯定也能成功。而且由于选木棒是从长到短选的,2、3互换的情况肯定早已出现过,并且没成功。
排除办法: 每次找一根木棒的时候,只要这不是一根棍子的第一条木棒,就不应该从下标为0的木棒开始找,而应该从刚刚(最近)接上去的那条木棒的下一条开始找。这样,就不会往2后面接更长的3了。
没有剪枝4的时候这组数据跑半天都出不了结果,有了之后很快就出来了:

64
40 40 30 35 35 26 15 40 40 40 40 40 40 40 40 40 40 40 40 40 40 

40 40 43 42 42 41 10 4 40 40 40 40 40 40 40 40 40 40 40 40 40 

40 25 39 46 40 10 4 40 40 37 18 17 16 15 40 40 40 40 40 40 40 

40

answer:
454

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int L, N, totalLen; 
int lastUsed;  //全局变量,剪枝4
int sticksLen[70];
bool Used[70];

int comp(const void *a, const void *b){
    return *(int *)b - *(int *)a;
}
/* 没有剪枝4的dfs函数
bool dfs(int stiLeft, int lenLeft){
    if(stiLeft == 0 && lenLeft == 0)
        return true;
    if(lenLeft == 0)
        lenLeft = L;
    for(int i = 0; i < N; i++){
        if(Used[i] == false && sticksLen[i] <= lenLeft){
            if(i > 0 && Used[i-1] == false && sticksLen[i] == sticksLen[i-1])
                continue;
            Used[i] = true;
            if(dfs(stiLeft - 1, lenLeft - sticksLen[i]))
                return true;
            Used[i] = false;
            if(lenLeft == L || lenLeft == sticksLen[i])
                return false;   
        }
    }
    return false;
}
*/
// stiLeft: 剩余没用过的木棍个数
// lenLeft: 要拼成L还剩的长度
bool dfs(int stiLeft, int lenLeft){
    if(stiLeft == 0 && lenLeft == 0)
        return true;
    if(lenLeft == 0)
        lenLeft = L;
    int startNo = 0;
    if(lenLeft != L)
        startNo = lastUsed + 1;
    for(int i = startNo; i < N; i++){
        if(Used[i] == false && sticksLen[i] <= lenLeft){
            //剪枝3
            if(i > 0 && Used[i-1] == false && sticksLen[i] == sticksLen[i-1])
                continue;
            Used[i] = true;
            lastUsed = i;
            if(dfs(stiLeft - 1, lenLeft - sticksLen[i]))
                return true;
            Used[i] = false;
            //剪枝1和剪枝2
            if(lenLeft == L || lenLeft == sticksLen[i])
                return false;   
        }
    }
    return false;
}

int main(){
    int i, j, k;
    while(scanf("%d", &N) != EOF){
        if(N == 0)  break;
        totalLen = 0;
        for(i = 0; i < N; i++){
            scanf("%d", &sticksLen[i]);
            totalLen += sticksLen[i];
        }
        qsort(sticksLen, N, sizeof(int), comp);
        for(L = sticksLen[0]; L <= totalLen/2; L++){
            if(totalLen % L)    continue;
            memset(Used, false, sizeof(Used));
            if(dfs(N, L)){
                printf("%d\n", L);
                break;
            }
        }
        if(L > totalLen/2)
            printf("%d\n", totalLen);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值