题解:#9280.「第9次PTA认证」竹简复原 审核通过

Wind_Rises 砂糖老师 2025-11-12 17:25:30 5

🧠 解题思路

1️⃣ 问题转化

假设原始竹简的标准长度为 len
那么我们需要判断:能否把所有片段分组,使得每组的和恰好为 len

这是一个典型的 分组搜索问题(类似于木棒拼接问题)。
但由于数据较多(n ≤ 50),暴力搜索需要优化。


2️⃣ 关键思路:剪枝 + 回溯

步骤如下:

  1. 计算片段总长度 sum
  2. 枚举可能的竹简标准长度 len
    • len 必须是 sum 的约数;
    • len >= max(a_i)
  3. 对每个可能的 len,用回溯搜索判断是否能分组成功。

剪枝技巧

  • 将片段按长度从大到小排序(优先使用大块能极大降低分支数)。
  • 当当前片段无法放入某篇竹简时,可以跳过相同长度的片段
  • 若当前篇竹简的第一个片段都无法放入,则直接回溯。
  • 若找到一种拼法,则立即输出。

3️⃣ 时间复杂度

虽然是回溯搜索,但在强剪枝下可以高效运行(n ≤ 50)。


💻 C++ 参考代码

#include <bits/stdc++.h>
using namespace std;

int n, total;
int a[70];
bool used[70];
int target, cnt;

bool dfs(int stick, int cur, int idx) {
    if (stick > cnt) return true; // 所有竹简拼完
    if (cur == target)
        return dfs(stick + 1, 0, 0); // 当前竹简拼好,开始下一篇

    int fail = 0;
    for (int i = idx; i < n; ++i) {
        if (used[i]) continue;
        if (cur + a[i] > target || a[i] == fail) continue;

        used[i] = true;
        if (dfs(stick, cur + a[i], i + 1)) return true;
        used[i] = false;

        fail = a[i]; // 剪枝:避免重复尝试同样长度的片段
        if (cur == 0 || cur + a[i] == target) return false;
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    int sum = 0;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        sum += a[i];
    }

    sort(a, a + n, greater<int>());
    for (target = a[0]; target <= sum; ++target) {
        if (sum % target != 0) continue; // 必须能整除
        cnt = sum / target;
        memset(used, false, sizeof(used));
        if (dfs(1, 0, 0)) {
            cout << target << "\n";
            return 0;
        }
    }
    return 0;
}
{{ vote && vote.total.up }}

共 1 条回复

root 站长

太强了%%%%