POJ 1011 递归 & 回溯

题目大意:给出一些长度不大于 50 的木棍, 要求你把这些小木棍拼成
*             长度相同木棍,当然长度越小越好。
* 解题思路:这个题最近做了很多次,我比较有发言权了。
*             思想很简单,一个接一个的把木棍拼起来,最后把木棍用光。
*             关键的地方是几个剪枝技巧:
*                   设所有木棍的总长度为 Sum, 最终的答案是 L。 
*             1. 首先要明白, Sum一定要能被 L 整除。 
*             2. L 一定 大于等于 题目给出的最长的木棍的长度 Max。
*                  由上述两点,我们想到,可以从 Max 开始递增地枚举 L, 
*                直到成功地拼出 Sum/L 支长度为 L 的木棍。
*                    搜索种的剪枝技巧: 
*             3. 将输入的输入从大到小排序,这么做是因为一支长度为 K 
*                的完整木棍,总比几支短的小木棍拼成的要好。
*                形象一些:
*                  如果我要拼 2 支长为8的木棍,第一支木棍我拼成 
*                          5 + 3
*                  然后拼第二支木棍但是失败了,而我手中还有长为 2 和 1 
*                  的木棍,我可以用 5 + 2 + 1 拼好第一支,再尝试拼第二
*                  支,仔细想一想,就会发现这样做没意义,注定要失败的。     
*                  我们应该留下 2+1 因为 2+1 比 3 更灵活。 
*             4. 相同长度的木棍不要搜索多次, 比如:
*                我手中有一些木棍, 其中有 2 根长为 4 的木棍, 当前搜索
*                状态是 5+4+.... (即表示长度为 5,4,2 的三支拼在一起, 
*                ...表示深层的即将搜索的部分), 进行深搜后不成功,故我
*                没必要用另一个 4 在进行 5+4+...
*             5. 将开始搜索一支长为 L 的木棍时,我们总是以当前最长的未
*                被使用的 木棍开始,如果搜索不成功,那么以比它短的开始
*                那么也一定不能取得全局的成功。因为每一支题目给出的木棍
*                都要被用到。
*                如果,有 
*                    4
*                    5 4 4 3 2
*                  想拼成长为 6 的木棍,那么从 5 开始, 但是显然没有能与 5
*                  一起拼成 6 的,那么我就没必要去尝试从 4 开始的,因为
*                  最终 5 一定会被遗弃。在拼第 2 3 ... 支木棍时,一样。 
*             6. 最后的最简单的一个就是,
*                      for(int i = 0; i < n; i++)
*                          for(int j = 0; j < n; j++)
*                               {}
*                与
*                      for(int i = 0; i < n; i++)
*                          for(int j = i+1; j < n; j++)
*                               {} 
*                的区别,这个不多说了。
*             7. 我用过的另一个剪枝,但是对 poj 的数据效果一般,
*                用一个数组, Sum[i] 保存 第 i 个木棍之后,即比第 i 枝
*                木棍短或与之相等所有的木棍的长度之和。
*                试想,如果剩余的所有木棍加在一起都不能和我当前的状态拼
*                出一直长为 L 的木棍(从长度来看),还有必要搜下去么?

上面是搜索剪枝的思路

转自:http://www.cnblogs.com/devil-91/archive/2012/08/03/2621787.html

AC Code1:(dfs使用返回值)

·当dfs使用返回值时,一般只考虑最表层就好。

 1 // 20141114.cpp : 定义控制台应用程序的入口点。
 2 //
 3
 4 #include "stdafx.h"
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <string>
 8 #include <stdlib.h>
 9 #include <algorithm>
10 #include <iostream>
11 #include <math.h>
12 #include <queue>
13 #include <stack>
14 using namespace std;
15 #define LL long long
16 #define N 100010
17 #define inf 2e9
18 #define sf(a) scanf("%d",&(a));
19
20 int sticks[65];
21 int used[65];
22 int n,len;
23
24 bool dfs(){
25     //另外一个版本,不使用返回值!  直接在递归中输出答案
26
27 }
28 bool dfs(int i,int l,int t)//i为当前试取的棍子序号,l为要拼成一根完整的棍子还需要的长度,t初值为所有棍子总长度
29 {
30     if(l==0)
31     {
32         t-=len;
33
34         if(t==0) return true;  //这是最底层的一个true ,之后一层层返回到最高层! 一旦确定,则证明此事得到正确答案!
35
36         for(i=0;used[i];++i);            //剪枝1:搜索下一根大棍子的时候,找到第一个还没有使用的小棍子开始
37
38         used[i]=1;                           //由于排序过,找到的第一根肯定最长,也肯定要使用,所以从下一根开始搜索
39         if(dfs(i+1,len-sticks[i],t)) return true;
40         used[i]=0;   //这里相当于还原!!!  这里一定要注意!  回溯法必须进行还原操作! 否则对于另外一种情况使用时会错误!
41
42         return false;
43         // t+=len;   //这个因为是局部变量,可以不还原,没意义
44     }
45     else
46     {
47         for(int j=i;j<n;++j)  //从i开始进行遍历
48         {
49             if(j>0&&(sticks[j]==sticks[j-1]&&!used[j-1]))  //剪枝2:前后两根长度相等时,如果前面那根没被使用,也就是由前面那根
50                 continue;                                                //开始搜索不到正确结果,那么再从这根开始也肯定搜索不出正确结果,此剪枝威力较大
51
52             if(!used[j]&&l>=sticks[j])   //剪枝3:最简单的剪枝,要拼成一根大棍子还需要的长度L>=当前小棍子长度,才能选用
53             {
54                 //l-=sticks[j];
55                 used[j]=1;
56                 if(dfs(j,l-sticks[j],t))return true;
57                 //l+=sticks[j];
58                 used[j]=0;  //这里两个都要进行还原
59             }
60         }
61         return false;
62     }
63 }
64 bool cmp(const int a, const int b)
65 {
66     return a>b;
67 }
68 int main()
69 {
70     while(cin>>n&&n)
71     {
72         int sum=0;
73         for(int i=0;i<n;++i)
74         {
75             cin>>sticks[i];
76             sum+=sticks[i];
77             used[i]=0;
78         }
79
80         sort(sticks,sticks+n,cmp);   //剪枝5:从大到小排序后可大大减少递归次数
81
82         bool flag=false;
83         for(len=sticks[0];len<=sum/2;++len)   //剪枝6:大棍长度一定是所有小棍长度之和的因数,且最小因数应该不小于小棍中最长的长度
84         {
85             if(sum%len==0)
86             {
87                 if(dfs(0,len,sum))
88                 {
89                     flag=true;
90                     cout<<len<<endl;
91                     break;
92                 }
93             }
94         }
95         if(!flag)  //一直没有成功,则进行需要一根sum长的木棍
96             cout<<sum<<endl;
97     }
98     return 0;
99 }

AC Code2(dfs 返回值为空):

·重点在于判断何时为最底层!并进行标记

// 20141114.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <string>
#include <stdlib.h>
#include <algorithm>
#include <iostream>
#include <math.h>
#include <queue>
#include <stack>
using namespace std;
#define LL long long
#define N 100010
#define inf 2e9
#define sf(a) scanf("%d",&(a));

int sticks[65];
int used[65];
int n,len;
bool flag;

void dfs(int i,int l,int t)//i为当前试取的棍子序号,l为要拼成一根完整的棍子还需要的长度,t初值为所有棍子总长度
{
    if(l==0){
        t-=len;
        if(t==0) {
            printf("%d\n",len); //这是最底层的一个true ,之后一层层返回到最高层! 一旦确定,则证明此事得到正确答案!
            flag=true;
        }
        if(!flag){
            for(i=0;used[i];++i);            //剪枝1:搜索下一根大棍子的时候,找到第一个还没有使用的小棍子开始
            used[i]=1;                           //由于排序过,找到的第一根肯定最长,也肯定要使用,所以从下一根开始搜索
            dfs(i+1,len-sticks[i],t);
            used[i]=0;   //这里相当于还原!!!  这里一定要注意!  回溯法必须进行还原操作! 否则对于另外一种情况使用时会错误!
        }
    }
    else
    {
        if(!flag){
            for(int j=i;j<n;++j)  //从i开始进行遍历
            {
                if(j>0&&(sticks[j]==sticks[j-1]&&!used[j-1]))  //剪枝2:前后两根长度相等时,如果前面那根没被使用,也就是由前面那根
                    continue;                                                //开始搜索不到正确结果,那么再从这根开始也肯定搜索不出正确结果,此剪枝威力较大

                if(!used[j]&&l>=sticks[j])   //剪枝3:最简单的剪枝,要拼成一根大棍子还需要的长度L>=当前小棍子长度,才能选用
                {
                    used[j]=1;
                    dfs(j,l-sticks[j],t);
                    used[j]=0;  //这里两个都要进行还原
                }
            }
        }
    }
}
bool cmp(const int a, const int b)
{
    return a>b;
}
int main()
{
    while(cin>>n&&n)
    {
        int sum=0;
        for(int i=0;i<n;++i)
        {
            cin>>sticks[i];
            sum+=sticks[i];
            used[i]=0;
        }

        sort(sticks,sticks+n,cmp);   //剪枝5:从大到小排序后可大大减少递归次数

        flag=false;
        for(len=sticks[0];len<=sum/2;++len)   //剪枝6:大棍长度一定是所有小棍长度之和的因数,且最小因数应该不小于小棍中最长的长度
        {
            if(sum%len==0)
            {
                dfs(0,len,sum);
                if(flag) break;
            }
        }
        if(!flag)  //一直没有成功,则进行需要一根sum长的木棍
            cout<<sum<<endl;
    }
    return 0;
}
时间: 2024-12-05 00:39:01

POJ 1011 递归 & 回溯的相关文章

POJ 2488 A Knight&#39;s Journey 递归回溯题解

简单的递归回溯法,锻炼基本的编程能力. 这类题是对代码能力的要求比对思想的要求高点. 而且要审题,题目要求安lexicographically 顺序输出,不小心递归的顺序就会输出错误了. 棋盘是由数字列或者行,和字母列或者行组成的,故此输出结果要注意. 个人觉得我的递归回溯写法是非常清晰, 工整的,O(∩_∩)O哈哈~ #include <stdio.h> #include <string.h> const int MAX_N = 27; bool board[MAX_N][MAX

POJ 1011 - Sticks DFS+剪枝

POJ 1011 - Sticks 题意:    一把等长的木段被随机砍成 n 条小木条    已知他们各自的长度,问原来这些木段可能的最小长度是多少 分析:    1. 该长度必能被总长整除    2. 从大到小枚举,因为小长度更灵活, 可拼接可不拼接    3. 因为每一跟木条都要用到, 故若轮到其中一根原始木段选它的第一根木条时,若第一根木条若不满足,则显然第一根木条在接下来任何一根原始木段都不会满足,故无解    4. 由于所有棒子已排序,在DFS时,若某根棒子未被选,则跳过其后面所有与

poj 1011 Sticks 【DFS】+【剪枝】

题意:有未知根(长度一样)木棒(小于等于n),被猪脚任意的截成n段,猪脚(脑抽了)想知道被截之前的最短长度(我推测猪脚得了健忘症). 这道题光理解题意就花了好久,大意就是任意根被截后的木棒拼到一起,能不能组成s(<=n)根的相同的木棒, 例:数据 9  5 1 2 5 1 2 5 1 2 可以组成最短为6 的(5+1, 2+2+2)3根木棒. 策略:深搜. 不过要是传统的深搜的话,TLE妥妥的.所以要剪枝(所谓剪枝,就是多加几个限制条件). 话不多说直接上代码. 代码1: #include <

poj 2246 递归 zoj 1094

#include <iostream>#include <algorithm>#include <stdio.h>#include <string.h>#include <cmath>#include <stack>using namespace std;struct node{ int m,n; // bool f;};node hash[200];char s[1000];int main(){ int i,n,sum; char

八皇后问题——递归+回溯法

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 高斯认为有76种方案.1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果. 求解过程: 采用遍历的办法,就是采用将每种情况都验证的办法最终找出问题的解,但是蛮力遍历的话,需要遍历的数据量太大,计算时间花费太大,所以在遍历

[POJ 1011]Sticks(DFS剪枝)

Description George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were original

N皇后问题(递归回溯)

今天讲了N后问题,现在来复习一下. N后问题就是在N*N格的棋盘上面放置彼此不受攻击的n个皇后.这里的皇后指的是国际象棋中的皇后,按照国际象棋的规则,皇后可以攻击当前行和当前列还有同一斜线的棋子.简单来说,就是n个皇后的位置不可以在同一行,同一列,同一斜线.因为这几天学习的是回溯算法,很简单的想到了回溯.这个问题也是经典的回溯算法习题之一. 下面来想一下问题所包含的条件,明显的条件就是棋盘是n*n,而且很简单就想到n个皇后不可以在同一列,就是每一列都会有一个皇后.下面就是隐式的条件了,同一行和同

通过C语言,利用递归回溯的方法,实现八皇后问题的求解

八皇后问题: 在国际象棋8*8的棋盘上,摆放八个皇后且皇后都无法吃掉对方,而八皇后的攻击路线 为它所在的列和行,还有45度斜线. 对于该问题,首先要确定递归的输入和输出,以及终止条件和方法.一个递归完成对当 前行皇后位置的确定,并通过遍历所有列,查找出所有可能.其中,利用对列的遍历实 现回溯. 具体实现方法,可以通过代码理解,以及思路参考https://blog.csdn.net/a304672343/article/details/8029122 参考博客的博主对于八皇后位置的表示处理的很好,

POJ 1011 Sticks 【DFS 剪枝】

题目链接:http://poj.org/problem?id=1011 Sticks Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 154895   Accepted: 37034 Description George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now