C++完整参考代码
#include <iostream>
#include <vector>
#include <cstdlib>
#include <functional>
#include <algorithm>
using namespace std;
int main() {
int a[] = {3, 3, 2, 2, 2};
int n = 5, sum = 0;
for (int i = 0; i < n; i++) sum += a[i];
// 方法一:贪心——从大到小,每次把木棍放进当前较短的一组
vector<int> v(a, a + n);
sort(v.begin(), v.end(), greater<int>());
int g1 = 0, g2 = 0;
for (int i = 0; i < n; i++) {
if (g1 <= g2) g1 += v[i];
else g2 += v[i];
}
cout << "贪心分组: " << g1 << " vs " << g2
<< " 差 = " << abs(g1 - g2) << endl;
// 方法二:0/1 背包 DP——dp[j] 表示能否凑出长度和恰好为 j
vector<bool> dp(sum + 1, false);
dp[0] = true;
for (int i = 0; i < n; i++)
for (int j = sum; j >= a[i]; j--) // 必须倒着更新
if (dp[j - a[i]]) dp[j] = true;
int best = 0;
for (int j = sum / 2; j >= 0; j--) // 找不超过一半的最大可达和
if (dp[j]) { best = j; break; }
cout << "最优分组: " << best << " vs " << sum - best
<< " 差 = " << sum - 2 * best << endl;
return 0;
}
答案:不能直接用贪心法。反例 [3, 3, 2, 2, 2]:贪心(从大到小轮流放进较短的一组)得到 {3, 2, 2} = 7 与 {3, 2} = 5,差 = 2;而最优分组是 {3, 3} = 6 与 {2, 2, 2} = 6,差 = 0。正确方法是 0/1 背包 DP:目标容量取 sum / 2,用 dp[j] 表示能否选出长度之和恰好为 j 的木棍子集,最终答案 = sum − 2 × (不超过 sum/2 的最大可达 j),时间复杂度 O(n × sum)。
思路:两组之差最小,等价于让其中一组尽量接近总和的一半。贪心只在「每一步都选当前最好」时才会全局最优,而本题某根木棍一旦选错组就无法回退——反例正说明贪心会栽在这种「局部最优 ≠ 全局最优」的场合。改成 DP 后,dp[j] 记录所有可能的和,不漏掉任何组合,所以一定得到最优解。
考点:贪心法的适用条件(有没有反例);0/1 背包 DP 的建模与滚动数组实现;sum − 2×best 这种「差值转凑和」的技巧。
易错:① 滚动数组里内层循环 j 必须从大到小(倒序),否则同一根木棍会被重复使用,变成「完全背包」;② 目标容量是 sum/2,要从 sum/2 往下找第一个可达的 j,而不是从 0 往上;③ 别用 sum/2 当答案,忘记乘 2 —— 差值公式是 sum − 2 × best;④ n 较小或 sum 很大时 DP 会偏慢,此时可改用 meet-in-the-middle 折半枚举。