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

信奥赛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,则尝试放入当前木棍,递归继续。
关键剪枝(大幅降低搜索量)
  1. 长度从大到小排序:先尝试较长的木棍,可减少分支数。
  2. 整除性剪枝tot 必须是总长度的约数,否则跳过。
  3. 拼接失败剪枝
    • rest == a[i](即当前木棍恰好被填满)且递归失败,说明后面无论怎么组合都无法成功,因为长度 a[i] 是当前这根的最后一块,若它不能成功,则任何其他长度代替它也无法成功(因为若换用更短的木棍,后面会更难拼)。
    • rest == tot(即当前木棍开始拼第一个木棍)且递归失败,说明这根木棍的第一个选择(长度为 a[i])不可行,并且由于木棍已从大到小排序,后续所有选择都会失败(因为第一个木棍无法匹配任何长度)。
  4. 跳过相同长度:如果当前木棍长度尝试失败,后面相同长度的木棍必然也失败,直接跳过。

这些剪枝使得搜索在数据范围(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. 关键步骤
  1. 读取输入,计算总长度 sum,并找到最长小木棍 mx
  2. 将小木棍按长度从大到小排序。
  3. 枚举 totmxsum,只考虑能整除 sumtot
  4. 对每个可行的 tot,调用 dfs 进行尝试。
  5. dfs 递归地填充每根原始木棍,利用剪枝及时返回失败。
  6. 一旦找到可行解,输出并结束。
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;
}
相关推荐
王老师青少年编程4 小时前
信奥赛C++提高组csp-s之搜索进阶(搜索剪枝核心思想 )
c++·dfs·csp·信奥赛·搜索剪枝·搜索优化
一拳一个呆瓜4 小时前
【STL】使用 C++ 标准库标头
c++·stl
王老师青少年编程5 小时前
信奥赛C++提高组csp-s之搜索进阶(搜索剪枝案例实践2)
c++·信奥赛·csp-s·提高组·搜索剪枝·生日蛋糕·最优性剪枝
c++之路5 小时前
C++ 设计模式全总结
java·c++·设计模式
c238565 小时前
c/c++中的多态(上)
开发语言·c++
彷徨而立5 小时前
【C++】介绍 std::ifstream 输入文件流
开发语言·c++
MC皮蛋侠客5 小时前
C++17 多线程系列(十):多线程性能优化——从测量到调优
c++·多线程
程序大视界6 小时前
【C++ 从基础到项目实战】C++(六):拷贝控制——浅拷贝与深拷贝,兼谈智能指针
开发语言·c++·cpp
代码中介商7 小时前
C++四大设计模式:单例、工厂、观察者、策略
java·c++·设计模式