信奥赛C++提高组csp-s之搜索进阶(搜索剪枝案例实践1)

小木棍 ------ 可行性剪枝的综合运用
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 50 50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
第一行是一个整数 n n n,表示小木棍的个数。
第二行有 n n n 个整数,表示各个木棍的长度 a i a_i ai。
输出格式
输出一行一个整数表示答案。
输入输出样例 1
输入 1
9
5 2 1 5 2 1 5 2 1
输出 1
6
说明/提示
对于全部测试点, 1 ≤ n ≤ 65 1 \leq n \leq 65 1≤n≤65, 1 ≤ a i ≤ 50 1 \leq a_i \leq 50 1≤ai≤50。
思路分析
本题是经典的"小木棍"搜索问题,目标是找到原始等长木棍的最小可能长度。
给定若干截断后的小木棍长度(每段 ≤ 50),原始木棍长度相同且未知,需要将小木棍拼回若干根原始长度的木棍。
由于原始长度一定大于等于最长小木棍,且总长度必须能被原始长度整除,因此从最长小木棍长度开始枚举可能的原始长度 tot(即每根原始木棍的长度),检查是否能将全部小木棍恰好拼成 sum/tot 根长度为 tot 的木棍。
搜索框架
- 状态 :
dfs(k, rest, st)表示当前正在拼第k根原始木棍,这根木棍还剩rest长度需要填充,从第st根小木棍开始尝试。 - 目标 :拼完所有
cnt = sum/tot根木棍,即k == cnt时返回true。 - 转移 :枚举未使用的小木棍,如果长度 ≤
rest,则尝试放入当前木棍,递归继续。
关键剪枝(大幅降低搜索量)
- 长度从大到小排序:先尝试较长的木棍,可减少分支数。
- 整除性剪枝 :
tot必须是总长度的约数,否则跳过。 - 拼接失败剪枝 :
- 当
rest == a[i](即当前木棍恰好被填满)且递归失败,说明后面无论怎么组合都无法成功,因为长度a[i]是当前这根的最后一块,若它不能成功,则任何其他长度代替它也无法成功(因为若换用更短的木棍,后面会更难拼)。 - 当
rest == tot(即当前木棍开始拼第一个木棍)且递归失败,说明这根木棍的第一个选择(长度为a[i])不可行,并且由于木棍已从大到小排序,后续所有选择都会失败(因为第一个木棍无法匹配任何长度)。
- 当
- 跳过相同长度:如果当前木棍长度尝试失败,后面相同长度的木棍必然也失败,直接跳过。
这些剪枝使得搜索在数据范围(n ≤ 65)内能够较快通过。
代码实现
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 70; // 最大木棍数+5
int n, tot, mx, cnt; // n: 小木棍总数, tot: 当前尝试的原始长度, mx: 最长小木棍, cnt: 原始木棍数量
int a[N]; // 存储每根小木棍的长度
bool vis[N]; // 标记小木棍是否已被使用
// 从大到小排序的比较函数
bool cmp(int x, int y) {
return x > y;
}
// 深度优先搜索
// k: 已经拼好的原始木棍数量(当前正在拼第 k+1 根,但参数k表示已完成的根数)
// rest: 当前正在拼的木棍还剩多少长度需要填充
// st: 从第 st 根小木棍开始尝试(避免重复搜索)
bool dfs(int k, int rest, int st) {
if (k == cnt) return true; // 所有原始木棍都拼好了,成功
if (rest == 0) // 当前这根已经拼满,开始拼下一根
return dfs(k + 1, tot, 1); // 下一根木棍的剩余长度为 tot,从头开始选木棍
for (int i = st; i <= n; i++) { // 从 st 开始枚举未使用的小木棍
if (!vis[i] && a[i] <= rest) { // 未使用且长度不超过剩余长度
vis[i] = true; // 尝试使用这根木棍
if (dfs(k, rest - a[i], i + 1)) // 递归,下一根木棍从 i+1 开始(因为已排序,避免重复)
return true;
vis[i] = false; // 回溯,撤销选择
// 剪枝1: 如果当前木棍恰好填满剩余长度,但后续失败,则任何其他木棍都无法替代
// 因为如果换成更短的木棍,后续组合只会更困难,所以直接返回 false
if (rest == a[i]) return false;
// 剪枝2: 如果这是当前木棍的第一根木棍(即 rest == tot),且尝试失败,
// 说明以这根木棍开头的所有情况都不可行,因为木棍从大到小排序,
// 后面更短的木棍作为第一根只会更差,直接返回 false
if (rest == tot) return false;
// 剪枝3: 跳过所有与当前失败木棍长度相同的后续木棍(它们也会失败)
while (i + 1 <= n && a[i + 1] == a[i]) i++;
}
}
return false; // 所有尝试均失败
}
int main() {
cin >> n;
int sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i]; // 计算总长度
}
sort(a + 1, a + n + 1, cmp); // 从大到小排序,便于剪枝
mx = a[1]; // 最长小木棍长度
// 枚举可能的原始木棍长度,从最长小木棍开始到总长度
for (tot = mx; tot <= sum; tot++) {
if (sum % tot != 0) continue; // 必须能整除,否则不可能拼成整数根
cnt = sum / tot; // 原始木棍的数量
memset(vis, 0, sizeof vis); // 重置访问标记
if (dfs(1, tot, 1)) { // 从第1根原始木棍开始,剩余长度为tot,从第1根小木棍开始尝试
cout << tot << endl; // 找到最小可行长度,输出并结束循环
break;
}
}
return 0;
}
功能分析
1. 程序目标
给定若干截断后的小木棍长度,找出原始等长木棍的最小可能长度。
2. 输入输出
- 输入:第一行整数 n(小木棍个数),第二行 n 个整数(每段长度)。
- 输出:一个整数,表示原始木棍的最小可能长度。
3. 算法核心
- 枚举原始长度
tot,利用深度优先搜索(DFS)判断是否能将所有小木棍拼成若干根长度为tot的木棍。 - 通过多重剪枝大幅减少搜索空间,从而在 n ≤ 65 的范围内可行。
4. 关键步骤
- 读取输入,计算总长度
sum,并找到最长小木棍mx。 - 将小木棍按长度从大到小排序。
- 枚举
tot从mx到sum,只考虑能整除sum的tot。 - 对每个可行的
tot,调用dfs进行尝试。 dfs递归地填充每根原始木棍,利用剪枝及时返回失败。- 一旦找到可行解,输出并结束。
5. 复杂度分析
- 最坏情况仍为指数级,但实际运行中剪枝效果显著,能通过本题数据范围。
- 排序 O(n log n),DFS 状态数受剪枝限制,通常能在合理时间内完成。
更多系列知识,请查看专栏:《信奥赛C++提高组csp-s知识详解及案例实践》:
https://blog.csdn.net/weixin_66461496/category_13113932.html
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
1、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html
2、csp信奥赛冲刺一等奖有效刷题题解:
信奥赛C++普及组csp-j初赛&复赛真题题解(持续更新) https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html
3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html
4、csp/信奥赛C++,完整信奥赛系列课程(永久学习):
https://edu.csdn.net/lecturer/7901 点击跳转
· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}