pku acm 2362 square 解题报告
2009-05-20 08:34
302 查看
[b]acm 2362 squarehttp://acm.pku.edu.cn/JudgeOnline/problem?id=2362 给定一些有特定长度的小棍,问能否拼成正方形。很经典的深度优先搜索+剪枝的题目。 1. 首先不能被4 整除的不行 排序后最长的木棍大于总长度/4的也不行,也就是大于唯一可能的正方形边长的。 2. 然后就是深度优先搜索, 举例说如果,长度总和,除以 4 就是正方形边长,我们设定为len 我们要找出是否存在一个分组,将小木棍分成四组,每组长度和为len 3. 两个典型的测试用例 1 3 4 5 4 7 8 可以 1 7 3 5 4 4 8 注意不能 1 3 4 凑成一组 5 4 7 8 8 不可以 4. 基本的DFS搜索思路 对于每个木棍,可以选或者不选两种状态,顺序扫描所有的木棍,然后找出所有可能的组合,使得长度和可能为len (对于给的例子即为8), 对于一个给定的长度为8的木棍组,继续递归寻找另一组,直到能找到所有组则成功,但是如果后续失败了, 还能返回上面,继续尝试 如 找到 1 3 4 然后 找到 8 然后不成功 还可以退回来 找到 1 7 5. 剪枝优化 5.1.其实我们只要找3个分组,对于总和为32的来说,找3个长度为8 的分组成功的话就成功了 5.2. 每次选组的时候,首元素确定。为什么要确定首元素,剪枝,(和排序无关) 因为本质上我们要找的4个分组是不分顺序的!! abc def ghi jkl 和 def jkl ghi abc是一个解 找到就可以 所以 对于 a 开头的组 如果我们试图找 (a,..) (...)(...) (...)失败了 那么没有必要找 (!a,....)(...)(...)(...)了 假如能找到 后面的成立 那么 a 必然在后面某一组 ,调到前面来也应该成立 矛盾了 这样每次找某个组的时候剪枝50% 5.3. 还有什么地方可以剪枝呢(这里也和排序无关) 当到达某个元素选定它的时候正好长度达到要求,如上面的例子 总和32,这个选的分组包含该元素正好长度为8. 1 3 4 4 5 7 8 扫描到4 发现长度和为8了 然后 找 (...) (...) (...)失败了 这时候只要退出到上一层继续就好了 不需要考虑 该层不选4的情况 当然对于 从大到小排列 很清楚 后面的更大 更不可能 6 2 1 1 1 1 1 1 1 1 1 1 对于这种情况,假如 扫描到2 发现正好为8,然后查找后续3组(...) (...)(...)失败 同样可以剪枝,不需要考虑 6 !2 。。。。。。。。。。。。 这种情况 因为加入后面的需要考虑 那么 令 x = 2 2前面的集合为 U sum( U + x) = len 最后假设分组成功 ,肯定 存在某个分组 sum(U + Z) = len sum(V+x) = len 那么完全可以互换 Z组和 x,这于前面 U+x 然后向后寻找其他组失败是矛盾的。 5.4.为什么要从大到小排列 POJ的测试数据证明了从大到小排序,快速的多,意味着更少的搜索。 首先从大到小,是先找数目少的集合,能较快的确定某个长度为len 的组, 而且确定的过程中经过许多单分支(仅 false) 8 7 5 4 4 3 1 如 找到 7 1 true false false false false true 而考虑 1 3 4 4 5 7 8 会先找到 1 3 4 后续分组不成功 退回来找 到 1 7 这个过程大量的搜索因为绝大多数分组都是2分的(选中或者不选),两个向下的分支。 下面注意是考虑从小到大排序, 0 右子树也会被剪枝掉, 0 7 1 0 7 4 1 4 0 7 8 4 5 1 8 4 4 0 7 4 9 5 7 12 5 8 5.5 找到后立即退出,也算剪枝。 方法.1 抛出异常 2. 适当的 return 语句 if (dfs(next)) return, 这样成功后立即向上逐层直接退出而不进入后续分支 choosen() if (dfs(next) return true not choosen() if dfs(next) return true return false 5. 6 一个小的剪枝 当两个元素相同,排序后紧挨着,前面的元素如果为被选中,后面的也不需要选。 否则重复了。 4 !4 !4 4 是相同的情形。 //代码主要参考了http://www.cppblog.com/jhpjhyx/archive/2009/05/14/82981.html 表示感谢:) #include<iostream> #include <algorithm> using namespace std; int test_num; int sl[20]; bool choosen[20]; int stick_num; bool find_square; int sum; int len; struct cmper{ inline bool operator ()(const int &a, const int &b)const{ return a > b; } }; //确保了最大的要小于= len,也正确 但递归调用较多,考虑用下面的循环减少递归 //void find_group(int set_num, int depth) { // if (depth == stick_num) // return; // if (set_num == 0) { // find_square = true; // throw true; // } // // if (choosen[depth] == true) // find_group(set_num, depth + 1); // else { // if (depth && !sl[depth - 1] && sl[depth] == sl[depth -1]) { // find_group(set_num, depth + 1); // return; // } // int sum_now = sl[depth] + sum; // if (sum_now < len) { // choosen[depth] = true; // sum = sum_now; // find_group(set_num, depth + 1); // sum = sum_now - sl[depth]; // choosen[depth] = false; // if (sum != 0) //剪枝,最大的只能被选中,不需要考虑不被选中的情况,剪枝50% // find_group(set_num, depth + 1); // } // else if (sum_now == len) { // choosen[depth] = true; // sum = 0; // find_group(set_num - 1, 0); // choosen[depth] = false; // // } // else { // find_group(set_num, depth + 1); //sum_now > len 只能不选当前的 // } // // } //} bool find_group(int set_num, int depth , int sum = 0) { if (set_num == 1) //选好3组了,返回成功 return true; for (int i = depth; i < stick_num; i++) { if (choosen[i] == true) //前面的选好的组已经用到该元素,略过继续查找。 continue; if (i && !sl[i - 1] && sl[i] == sl[i -1]) //剪枝,作用不大 continue; int sum_now = sl[i] + sum; if (sum_now < len) { choosen[i] = true; //选中当前元素,继续后续查找当前组组元素 if (find_group(set_num, i + 1, sum_now)) return true; choosen[i] = false; //不选中当期元素的情形(注意向上返回时都已置回false),继续后续查找当前组元素 } else if (sum_now == len) { choosen[i] = true; if (find_group(set_num - 1, 0, 0)) //找到1组,查找后续分组,成功的话返回true return true; choosen[i] = false; } if (sum_now == len || sum == 0) //剪枝,首元素要被选中,当包含首元素的组查找其它分组失败即为失败5.2 break; //当选中当前元素,能构成一个组的时候,如果查找后续分组失败即为失败5.3 } return false; //当前选择的情形,已经扫描到末尾仍为成功失向上层返回标识为失败 } bool group_stick() { find_square = false; for (int i = 0; i < 20; i++) choosen[i] = false; //try{ return find_group(4, 0); //} catch(bool nothing){ } //return find_square; } int main(int argc, char *argv[]) { cin >> test_num; int sum; for (int i = 0; i < test_num; i++) { cin >> stick_num; sum = 0; for (int j = 0; j < stick_num; j++) { cin >> sl[j]; sum += sl[j]; //choosen[i] = false; } if ((sum % 4) != 0) cout << "no" << endl; else { sort(sl, sl + stick_num, cmper()); len = sum / 4; if (sl[0] > len) cout << "no" << endl; else { if (group_stick()) cout << "yes" << endl; else cout << "no" << endl; } } } return 0; }
[/b]
相关文章推荐
- Pku acm 2250 Compromise 动态规划题目解题报告(六)
- Pku acm 1423 Big Number 解题报告----求n!的位数
- POJ 1005I Think I Need a Houseboat解题报告——【PKU ACM】
- 聪明的打字员 -- ACM PKU 1184 解题报告
- 整数分解和划分 - 兼 ACM PKU POJ 1221 解题报告
- Pku acm 1157 LITTLE SHOP OF FLOWERS 动态规划题目解题报告(十四)
- Pku acm 1274 The Perfect Stall 数据结构题目解题报告(十三)---- 匈牙利算法求二分图的最大匹配
- POJ 1006Biorhythms解题报告——生理周期——【PKU ACM】
- 单调队列 - 兼 ACM PKU POJ 3250 及 2823 解题报告
- Pku acm 2075 Tangled in Cables数据结构题目解题报告(十一)最小生成树:prim算法&amp;amp;二叉查找树
- Pku acm 1080 Humman Gene Function 动态规划题目解题报告(八)
- Pku acm 1088 滑雪 动态规划题目解题报告(十五)
- Pku acm 2418 Hardwood Species 排序算法解题报告(六) ----二叉查找数(BST)
- POJ 1007DNA Sorting解题报告——【PKU ACM】
- 【Jason's_ACM_解题报告】Fill the Square
- ACM pku 2719 解题报告(都是输入输出惹的祸)
- Pku acm 1125 Stockbroker Grapevine 动态规划题目解题报告(十九)
- Pku acm 1002 487-3279 排序算法解题报告(一) ----二叉查找数(BST)
- Pku acm 2075 Tangled in Cables数据结构题目解题报告(十一)最小生成树:prim算法&amp;amp;二叉查找树
- Pku acm 1466 Girls and Boys数据结构题目解题报告(十七)---- 匈牙利算法求二分图的最大匹配