因为整除只是必要条件而非充分条件,即使sum%k==0,仍需DFS回溯或状压DP验证能否实际划分为k个和为target的不相交非空子集,关键在于剪枝(降序排序、跳过重复、子集为空时只试首元素、超限即退)与状态设计(如dp[mask]表示mask能否凑出若干完整子集)。

为什么 std::accumulate 加总后不能直接除以 K 就完事?
因为整除只是必要条件,不是充分条件。即使 sum % k == 0,也可能无法划分——比如数组是 [1, 2, 4],k = 3,总和为 7,不满足整除;但换成 [1, 1, 1, 1, 2, 2],总和为 8,k = 4 → 目标子集和为 2,看似可行,可实际只有两个 2 和四个 1,无法凑出四个「和为 2」的不相交子集(每个子集至少要一个元素,而 2 单独占一个子集后,剩下全是 1,没法再凑三个和为 2 的子集)。所以必须回溯或状压搜索可行分配。
用 DFS + 剪枝判断划分是否可行的关键点
目标是把数组分成 k 个非空子集,每个子集和等于 target = sum / k。DFS 每次尝试把一个未用数字填进当前正在构造的子集里,填满一个就换下一个子集。核心在于剪枝,否则指数级爆炸:
-
先降序排序数组:让大数优先被分配,快速触发失败(比如某个大数 >
target,直接返回false) -
跳过重复值在相同子集位置的尝试:若
nums[i] == nums[i-1]且used[i-1] == false,说明上一个相同数没被选进当前子集,那这个也不该选(避免重复分支) -
子集为空时只试第一个未用数:防止
[2,2,2,2]中四个子集被排列成不同顺序(本质相同),即一旦current_sum == 0,只让nums[0]开头,其余跳过 -
当前子集和超限立即回退:
current_sum + nums[i] > target就break(因已降序)
DP + 位运算(状压)解法中 __builtin_popcount 和状态转移怎么配合?
状态 mask 是一个整数,第 i 位为 1 表示 nums[i] 已被使用。定义 dp[mask] 为当前已选数字集合对应的最大「完整子集数」,或者更常用的是:该状态下「当前正在填的子集还差多少和」——但更稳的做法是存余数:dp[mask] = (sum(mask) % target),仅当余数为 0 才表示刚好填满若干完整子集。
不过更主流的状压写法是:
立即学习“C++免费学习笔记(深入)”;
-
dp[mask]表示能否用mask对应的数字凑出若干个完整子集(即无剩余) - 转移时枚举子集
sub⊆mask,满足sum(sub) == target,且dp[mask ^ sub] == true - 但枚举所有子集是
O(3^n),不可取;实际用「从 mask 中去掉一个合法元素」递推,配合预处理每个mask的和:bitsum[mask] = bitsum[mask & (mask-1)] + nums[ctz(mask)] - 真正实用的优化是:只在
bitsum[mask] % target == 0时才计算dp[mask],且用dp[mask] = dp[mask ^ last](last是mask中最后一个置位,且bitsum[mask] - nums[last] )
遇到 std::vector<bool></bool> 导致位操作异常怎么办?
别用 std::vector<bool></bool> 存 used 状态做 DFS——它是特化模板,operator[] 返回代理对象而非引用,会导致 used[i] = true 失效或行为未定义。实操中一律换为 std::vector<char></char> 或 std::vector<int></int>。
同样,状压中若手动写 bit 操作(如 mask & (1 ),确保 <code>i 在 [0, n) 范围内,且 mask 类型为 unsigned int 或 uint32_t(n ≤ 32 时);超过 32 推荐用 std::bitset 或 __int128(GCC 扩展),但 LeetCode 等平台通常限制 n ≤ 16,1 完全够用。
边界容易漏:k == 1 必然 true;k > n 必然 false;任意 nums[i] > target 必然 false——这些得在 DFS/DP 前直接判掉。


















