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 }