木棒拼接问题 洛谷p1120

题目:木棒拼接问题

问题描述
给定 ( n ) 根木棒,每根木棒有一个长度。要求将这些木棒拼接成若干根长度相等的原始木棒,且每根原始木棒的长度尽可能短。求最短的原始木棒长度。


解题思路

1. 问题分析
  • 我们需要将所有木棒拼接成若干根长度相等的原始木棒。
  • 原始木棒的长度必须是所有木棒长度总和的约数。
  • 目标是找到最短的原始木棒长度。
2. 关键步骤
  1. 计算总长度

    • 计算所有木棒的长度总和 ( sum )。
  2. 枚举约数

    • 找到 ( sum ) 的所有约数,并按从小到大排序。
    • 约数是可能的原始木棒长度。
  3. DFS 搜索

    • 对每个约数 ( i ),尝试将所有木棒拼接成若干根长度为 ( i ) 的原始木棒。
    • 使用 DFS 进行搜索,尝试拼接木棒。
  4. 剪枝优化

    • 降序排序:将木棒按长度从大到小排序,优先尝试长木棒。
    • 跳过重复长度:如果当前木棒长度与前一根相同,且前一根未使用,则跳过当前木棒。
    • 长木棒优先:如果当前木棒是某根原始木棒的第一根木棒,且无法拼接成功,则直接返回 false
3. DFS 实现
  • 参数

    • i:当前尝试的原始木棒长度。
    • k:已经拼好的原始木棒数量。
    • ans:当前原始木棒已经拼接的长度。
    • start:当前木棒的起始位置(用于剪枝)。
  • 逻辑

    • 如果拼好了所有原始木棒(( k == sum / i )),返回 true
    • 遍历木棒,尝试拼接当前原始木棒。
    • 如果当前原始木棒拼接完成,开始拼接下一根原始木棒。
    • 如果当前原始木棒未拼接完成,继续拼接。
    • 回溯时取消标记。
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 的木棒。

复杂度分析

  1. 时间复杂度

    • 计算约数:( O(\sqrt{sum}) )。
    • DFS:通过剪枝优化,搜索空间显著减少。
  2. 空间复杂度

    • 主要用于存储约数和标记数组,空间复杂度为 ( O(n) )。

总结

通过枚举约数、DFS 搜索和剪枝优化,可以高效地解决木棒拼接问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值