hdu 1455 Sticks(dfs+剪枝)

题目大意:

  George有许多长度相同的木棍,随机的将这些木棍砍成小木条,每个小木条的长度都是整数单位(长度区间[1, 50])。现在George又想把这些小木棒拼接成原始的状态,但是他忘记了原来他有多少根木棍,也忘记了木棍的长度。现在请你编写一个程序,找到最短的原始的木棍长度。

输入:

  每个测试案例包含两行,第一行表示小木条的总个数(最多64根小木条),第二行表示每个小木条的长度,用空格分开。当第一行输入为零的时候表示程序结束。

输出:

  对于每个测试案例输出最短的木棍长度。

解题思路:

  用搜索算法去解决问题需要知道搜索的区间和剪枝条件,现在木棍长度未知,木棍个数未知,对于我们搜索是不利的。需要从已知条件推导到一个可以搜索的区间,然后再仔细思考如何剪枝来减少时间复杂度。

  小木条是从木棍上砍下来的 => 木棍长度的下限:最长的小木条长度(part_max)

  小木条的个数和长度已知    => 木棍长度的上限:所有小木条长度之和(part_sum)

  确定搜索区间:木棍长度(stick_len) ∈ [part_max, part_sum]  并且 stick_len ∈ 整数

  确定了stick_len => 确定了木棍的个数(stick_num)

  剪枝1:当确定一个stick_len时候,那么这个stick_len要能整除part_sum,否则不能组成整数stick_num

  剪枝2:见AC代码分析

  剪枝3:见AC代码分析

  

AC代码:

  1 // 19472897    2017 - 01 - 02 12:45 : 00    Accepted    1455    15MS    1724K    2328 B    C++    潮州牛肉丸
  2 #include <stdio.h>
  3 #include <algorithm>
  4 #include <functional>
  5 
  6 int n;                 // the number of parts
  7 int part[64];         // the parts at most 64
  8 int visit[64];         // mark the situation of already used parts
  9 int part_sum = 0;     // 所有小木条的长度之和
 10 int stick_num = 0;   // 木棍的个数
 11 int stick_len = 0;   // 木棍的长度
 12 
 13 void init()
 14 {
 15     part_sum = 0;
 16     stick_num = 0;
 17     memset(part, 0, sizeof(part));
 18     memset(visit, 0, sizeof(visit));
 19 }
 20 
 21 // count:已经拼接好的木棍数,len:正在拼接的木棍长度,index:上一次搜索下标位置
 22 bool dfs(int count, int len, int index)
 23 {
 24     if (stick_num == count)
 25         return true;
 26 
 27     for (int i = index + 1; i < n; ++i)
 28     {
 29         if (true == visit[i])
 30             continue;
 31         if (len + part[i] == stick_len)
 32         {
 33             visit[i] = true;
 34             if (true == dfs(count + 1, 0, -1))
 35                 return true;
 36             visit[i] = false;
 37             return false;
 38         }
 39         else if (len + part[i] < stick_len)
 40         {
 41             visit[i] = true;
 42             if (true == dfs(count, len + part[i], i))
 43                 return true;
 44             visit[i] = false;
 45 
 46             /* 已知条件:
 47                1、stick的长度(stick_num)和个数(stick_num)
 48                2、dfs已经返回false
 49             */
 50 
 51             /* 剪枝2:当dfs返回false,len等于0的时候,stick的长度不是题目的正确结果,
 52                后面的搜索无意义,直接返回false。
 53             
 54                根据原则:如果当前搜索用的stick长度是题目的正确结果,那么所有的part都一定要被用上。
 55                然而拼接一根新stick的时候(即len的值为0,表示当前正在拼接一根新stick),选取
 56                的第一根part无论如何都能被用上,只是后面的part去配合第一根part完成一根stick,
 57                如果在用第一根part的时候,dfs任然返回false,表示这个part不能被用上拼接stick。
 58                与原则相违背。
 59             */
 60             if (0 == len)
 61                 return false;
 62 
 63             /* 剪枝3:
 64                当前状态:dfs返回false,说明part[i]这根木条在当前count,len状态不可用,
 65                接下来搜索的part[i+1]如果与之前的part[i]长度相同也一定不可用,注意我们是把part按降序排序过的*/
 66             while (i < n && part[i] == part[i + 1])
 67                 ++i;
 68         }
 69     }
 70     return false;
 71 }
 72 
 73 int main(void)
 74 {
 75     while (scanf("%d", &n) != EOF && 0 != n)
 76     {
 77         init();
 78         for (int i = 0; i < n; ++i)
 79         {
 80             scanf("%d",    &part[i]);
 81             part_sum += part[i]; //记录所有小木条长度之和
 82         }
 83 
 84         std::sort(part, part + n, std::greater<int>()); // 按照降序排序
 85         
 86         // stick的可能长度区间:[最长的小木条长度,所有小木条长度之和]
 87         for (stick_len = part[0]; stick_len < part_sum; ++stick_len) 
 88         {
 89             if (0 == part_sum % stick_len) //剪枝1:木棍的长度一定要能整除所有小木条长度之和
 90             {
 91                 stick_num = part_sum / stick_len; // 得到stick的个数
 92                 if (true == dfs(0, 0, -1))
 93                     break;
 94             }
 95         }
 96 
 97         printf("%d\n", stick_len);
 98     }
 99     return 0;
100 }

 

posted on 2017-01-02 14:22  yqzheng  阅读(124)  评论(0编辑  收藏  举报

导航