题目:木棒拼接问题
问题描述:
给定 ( n ) 根木棒,每根木棒有一个长度。要求将这些木棒拼接成若干根长度相等的原始木棒,且每根原始木棒的长度尽可能短。求最短的原始木棒长度。
解题思路
1. 问题分析
- 我们需要将所有木棒拼接成若干根长度相等的原始木棒。
- 原始木棒的长度必须是所有木棒长度总和的约数。
- 目标是找到最短的原始木棒长度。
2. 关键步骤
-
计算总长度:
- 计算所有木棒的长度总和 ( sum )。
-
枚举约数:
- 找到 ( sum ) 的所有约数,并按从小到大排序。
- 约数是可能的原始木棒长度。
-
DFS 搜索:
- 对每个约数 ( i ),尝试将所有木棒拼接成若干根长度为 ( i ) 的原始木棒。
- 使用 DFS 进行搜索,尝试拼接木棒。
-
剪枝优化:
- 降序排序:将木棒按长度从大到小排序,优先尝试长木棒。
- 跳过重复长度:如果当前木棒长度与前一根相同,且前一根未使用,则跳过当前木棒。
- 长木棒优先:如果当前木棒是某根原始木棒的第一根木棒,且无法拼接成功,则直接返回
false
。
3. DFS 实现
-
参数:
i
:当前尝试的原始木棒长度。k
:已经拼好的原始木棒数量。ans
:当前原始木棒已经拼接的长度。start
:当前木棒的起始位置(用于剪枝)。
-
逻辑:
- 如果拼好了所有原始木棒(( k == sum / i )),返回
true
。 - 遍历木棒,尝试拼接当前原始木棒。
- 如果当前原始木棒拼接完成,开始拼接下一根原始木棒。
- 如果当前原始木棒未拼接完成,继续拼接。
- 回溯时取消标记。
- 如果拼好了所有原始木棒(( k == sum / i )),返回
4. 剪枝优化
- 降序排序:优先尝试长木棒,减少搜索空间。
- 跳过重复长度:避免重复计算。
- 长木棒优先:如果长木棒无法拼接成功,直接返回
false
。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring> // 包含 memset 函数的头文件
using namespace std;
const int N = 70;
int a[N]; // 存储木棒长度
vector<int> v; // 存储 sum 的所有约数
int n, sum = 0; // n 是木棒数量,sum 是木棒长度总和
bool vis[N]; // 标记木棒是否已使用
// 计算 sum 的所有约数
void cal_yueshu(int sum) {
for (int i = 1; i * i <= sum; i++) {
if (sum % i == 0) {
v.push_back(i); // 添加较小的约数
if (sum / i != i) {
v.push_back(sum / i); // 添加较大的约数
}
}
}
sort(v.begin(), v.end()); // 对约数排序
}
// DFS 函数
// i: 原始木棒长度
// k: 已经拼好的木棒数量
// ans: 当前木棒已经拼接的长度
// start: 当前木棒的起始位置(用于剪枝)
bool dfs(int i, int k, int ans, int start) {
if (k == sum / i) return true; // 如果拼好了所有木棒,返回 true
for (int j = start; j <= n; j++) {
if (vis[j] || ans + a[j] > i) continue; // 如果木棒已使用或超出长度,跳过
vis[j] = true; // 标记木棒已使用
if (ans + a[j] == i) { // 如果当前木棒拼接完成
if (dfs(i, k + 1, 0, 1)) return true; // 开始拼接下一根木棒
} else if (dfs(i, k, ans + a[j], j + 1)) { // 继续拼接当前木棒
return true;
}
vis[j] = false; // 回溯,取消标记
// 剪枝:如果当前木棒是某根原始木棒的第一根木棒,且无法拼接成功,则直接返回 false
if (ans == 0) return false;
// 剪枝:跳过相同长度的木棒
while (j + 1 <= n && a[j] == a[j + 1]) j++;
}
return false; // 如果没有找到解,返回 false
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
sort(a + 1, a + 1 + n, greater<int>()); // 对木棒长度降序排序
cal_yueshu(sum); // 计算 sum 的所有约数
for (auto i : v) {
memset(vis, false, sizeof(vis)); // 重置 vis 数组
if (dfs(i, 0, 0, 1)) { // 调用 DFS 函数
cout << i << endl; // 输出最短的原始木棒长度
break;
}
}
return 0;
}
示例输入输出
输入:
9
5 2 1 5 2 1 5 2 1
输出:
6
解释:
- 木棒长度总和为 24。
- 可能的原始木棒长度为 6。
- 可以将木棒拼接为 4 根长度为 6 的木棒。
复杂度分析
-
时间复杂度:
- 计算约数:( O(\sqrt{sum}) )。
- DFS:通过剪枝优化,搜索空间显著减少。
-
空间复杂度:
- 主要用于存储约数和标记数组,空间复杂度为 ( O(n) )。
总结
通过枚举约数、DFS 搜索和剪枝优化,可以高效地解决木棒拼接问题。