CSP-J系列【2024】P11229 [CSP-J 2024] 小木棍题解

题目描述

小 S 喜欢收集小木棍。在收集了 n 根长度相等的小木棍之后,他闲来无事,便用它们拼起了数字。用小木棍拼每种数字的方法如下图所示。

现在小 S 希望拼出一个整数,满足如下条件:

  • 拼出这个数恰好使用 n 根小木棍;
  • 拼出的数没有前导 0;
  • 在满足以上两个条件的前提下,这个数尽可能小。

小 S 想知道这个数是多少,可 n 很大,把木棍整理清楚就把小 S 折腾坏了,所以你需要帮他解决这个问题。如果不存在正整数满足以上条件,你需要输出 −1 进行报告。

输入格式

本题有多组测试数据。

输入的第一行包含一个正整数 T,表示数据组数。

接下来包含 T 组数据,每组数据的格式如下:

一行包含一个整数 n,表示木棍数。

输出格式

对于每组数据:输出一行,如果存在满足题意的正整数,输出这个数;否则输出 −1。

输入输出样例

输入 #1复制

复制代码
5
1
2
3
6
18

输出 #1复制

复制代码
-1
1
7
6
208

说明/提示

【样例 1 解释】

  • 对于第一组测试数据,不存在任何一个正整数可以使用恰好一根小木棍摆出,故输出 −1。
  • 对于第四组测试数据,注意 0 并不是一个满足要求的方案。摆出 9、41 以及 111 都恰好需要 6 根小木棍,但它们不是摆出的数最小的方案。
  • 对于第五组测试数据,摆出 208 需要 5+6+7=18 根小木棍。可以证明摆出任何一个小于 208 的正整数需要的小木棍数都不是 18。注意尽管拼出 006 也需要 18 根小木棍,但因为这个数有前导零,因此并不是一个满足要求的方案。

【数据范围】

对于所有测试数据,保证:1≤T≤50,1≤n≤105。

测试点编号 n≤ 特殊性质
1 20
2 50
3 103 A
4,5 105 A
6 103 B
7,8 105 B
9 103
10 105

特殊性质 A:保证 n 是 7 的倍数且 n≥100。

特殊性质 B:保证存在整数 k 使得 n=7k+1,且 n≥100。

题目概述与分析

小木棍问题是2024年CSP-J竞赛中的一道经典题目,考察选手对搜索算法和剪枝优化的掌握。题目描述有n根长度不同的小木棍,需要将它们拼接成若干根长度相同的长木棍,求可能的最小原始长木棍长度。

这个问题可以建模为组合优化问题,需要综合考虑所有可能的组合方式。我们将介绍两种不同的解法:深度优先搜索(DFS)剪枝法和动态规划法。

解法一:DFS剪枝法

算法思路

  1. 使用深度优先搜索尝试所有可能的组合
  2. 通过多种剪枝策略优化搜索效率
  3. 从最大可能长度开始向下搜索
  4. 时间复杂度取决于剪枝效果,最坏情况下O(n!)
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

vector<int> sticks;
vector<bool> used;
int n, total_len, max_len;

bool dfs(int cnt, int cur_len, int start, int target) {
    if (cnt == total_len / target) return true;
    if (cur_len == target) return dfs(cnt + 1, 0, 0, target);
    
    for (int i = start; i < n; ++i) {
        if (!used[i] && cur_len + sticks[i] <= target) {
            if (i > 0 && sticks[i] == sticks[i-1] && !used[i-1]) continue;
            used[i] = true;
            if (dfs(cnt, cur_len + sticks[i], i + 1, target)) return true;
            used[i] = false;
            if (cur_len == 0) break;
        }
    }
    return false;
}

int main() {
    cin >> n;
    sticks.resize(n);
    used.resize(n, false);
    total_len = 0;
    max_len = 0;
    
    for (int i = 0; i < n; ++i) {
        cin >> sticks[i];
        total_len += sticks[i];
        max_len = max(max_len, sticks[i]);
    }
    
    sort(sticks.begin(), sticks.end(), greater<int>());
    
    for (int len = max_len; len <= total_len; ++len) {
        if (total_len % len != 0) continue;
        fill(used.begin(), used.end(), false);
        if (dfs(0, 0, 0, len)) {
            cout << len << endl;
            return 0;
        }
    }
    
    cout << total_len << endl;
    return 0;
}

该实现使用DFS加多种剪枝策略,包括排序优化、跳过重复值和提前终止等,能有效提高搜索效率。

解法二:动态规划法

算法思路

  1. 将问题转化为背包问题的变种
  2. 使用位运算状态压缩
  3. 记录可达的长度组合
  4. 时间复杂度O(n*sum),适合sum较小的情况
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <bitset>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> sticks(n);
    int sum = 0;
    
    for (int i = 0; i < n; ++i) {
        cin >> sticks[i];
        sum += sticks[i];
    }
    
    sort(sticks.begin(), sticks.end());
    
    for (int len = sticks.back(); len <= sum; ++len) {
        if (sum % len != 0) continue;
        
        int k = sum / len;
        vector<bitset<100>> dp(k + 1);
        dp[0][0] = 1;
        
        for (int stick : sticks) {
            for (int i = k; i >= 0; --i) {
                for (int j = len; j >= 0; --j) {
                    if (dp[i][j]) {
                        if (j + stick <= len) {
                            dp[i][j + stick] = 1;
                        }
                        if (i + 1 <= k && stick <= len) {
                            dp[i + 1][stick] = 1;
                        }
                    }
                }
            }
        }
        
        if (dp[k][0]) {
            cout << len << endl;
            return 0;
        }
    }
    
    cout << sum << endl;
    return 0;
}

动态规划解法通过状态压缩记录可达的长度组合,适合小规模数据,但空间复杂度较高。

算法对比分析

方法 时间复杂度 空间复杂度 适用场景
DFS剪枝法 取决于剪枝效果 O(n) 数据规模中等,剪枝有效
动态规划法 O(n*sum) O(k*len) sum值较小的情况

关键问题详解

剪枝策略优化

  1. 从大到小排序木棍,优先尝试长木棍
  2. 跳过相同长度的重复尝试
  3. 当前组合失败时及时回溯
  4. 限制搜索的起始位置避免重复

边界情况处理

  1. 所有木棍长度相同的情况
  2. 无法分割的情况
  3. 极端大规模数据测试
  4. 包含长度为1的木棍的情况

性能优化建议

  1. 预处理时计算所有可能的候选长度
  2. 使用更高效的数据结构存储状态
  3. 并行化处理可能的候选长度
  4. 提前终止不可能的组合

数学原理分析

  1. 组合数学中的分割问题
  2. 数论中的因数分解应用
  3. 搜索算法的剪枝理论
  4. 动态规划的最优子结构性质

实际应用价值

这类算法在以下领域有重要应用:

  1. 资源分配与调度
  2. 货物装载优化
  3. 时间表安排
  4. 工业生产中的材料切割

扩展思考

  1. 如果木棍有不同颜色限制如何处理?
  2. 如果允许部分不匹配的情况怎么解决?
  3. 如何扩展到三维空间的材料分割?
  4. 多目标优化情况下如何调整算法?

掌握这类组合优化问题的解法,不仅能提升竞赛能力,也对解决实际工程问题有很大帮助。建议先理解基础算法原理,再针对具体问题特点进行优化。

相关推荐
科大饭桶2 小时前
数据结构自学Day13 -- 快速排序--“分而治之”
数据结构·算法·排序算法
某个默默无闻奋斗的人3 小时前
【矩阵专题】Leetcode54.螺旋矩阵(Hot100)
java·算法·leetcode
JXL18604 小时前
Leetcode-.42接雨水
算法·leetcode·职场和发展
啊我不会诶5 小时前
CF每日5题(1500-1600)
c++·学习·算法
巴伦是只猫6 小时前
Java 高频算法
java·开发语言·算法
点云SLAM6 小时前
C++中std::string和std::string_view使用详解和示例
开发语言·c++·算法·字符串·string·c++标准库算法·string_view
88号技师6 小时前
2025年7月Renewable Energy-冬虫夏草优化算法Caterpillar Fungus Optimizer-附Matlab免费代码
开发语言·人工智能·算法·matlab·优化算法
dragoooon347 小时前
优选算法:移动零
c++·学习·算法·学习方法
运维小文7 小时前
初探贪心算法 -- 使用最少纸币组成指定金额
c++·python·算法·贪心算法
智者知已应修善业8 小时前
【C# 找最大值、最小值和平均值及大于个数和值】2022-9-23
经验分享·笔记·算法·c#