0/1 背包判断能否凑满 sum/2,倒序更新避免重复使用元素。
OJ: leetcodecn
题目 ID: partition-equal-subset-sum
难度:普及+/提高-
标签:动态规划0/1背包
创建: 2026-07-29 12:47
更新: 2026-07-29 15:20
后置题目
shumeng CSP202603B机器人项目管理B 的普通任务部分复用 A 的 0/1 背包倒序更新这一步:dp[u] 取 max(dp[u], dp[u-c]+g) 的背包转移保证每件任务只选一次,再叠加 A 未教的灵活任务按单位收益贪心与两类任务按杯数合并枚举。luogu P7097[yLOI2020] 牵丝戏B 的正式解第一步就是 A 教的那类 0/1 背包判定/最值转移:main.cpp:41 用 for (s=200; s>=p[i]; s--) 容量倒序枚举保证每种道具本回合最多选一次,把 A 的“dp[和] 可达 + 倒序避免重复选”换成 best_k[总 p]=最大总 k,之后才叠加极小极大 DP 这一新台阶。luogu P4138[JOISC 2014] 挂饰 / StrapsB 的第 3 步把 A 教的「选子集问题写成 dp[累积量]、每件物品至多选一次的 0/1 背包」原样搬来,只把 A 的和维可达性 dp[i] 换成挂钩数维的最小损失 dp[j](main.cpp:66-92 逐挂饰转移),A 未教的台阶是 sum(A)>=k-1 可行性观察与必选/必不选分类。
题意
判断数组能否分成两个和相等的子集。
思路
若总和为奇数,不可能。否则目标为 sum/2,转化为 0/1 背包:从 nums 中选若干数,和恰好为 target。dp[i] 表示和 i 是否可达,倒序更新避免重复使用同一元素。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool canPartition(vector<int> &nums) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum & 1)
return false;
int target = sum / 2;
vector<bool> dp(target + 1, false);
dp[0] = true;
for (int x : nums)
for (int i = target; i >= x; i--)
dp[i] = dp[i] || dp[i - x];
return dp[target];
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
for (int &x : a)
cin >> x;
cout << Solution().canPartition(a) << '\n';
return 0;
}python
#!/usr/bin/env python3
from typing import List
class Solution:
def canPartition(self, nums: List[int]) -> bool:
s = sum(nums)
if s & 1:
return False
target = s // 2
dp = [False] * (target + 1)
dp[0] = True
for x in nums:
for i in range(target, x - 1, -1):
dp[i] = dp[i] or dp[i - x]
return dp[target]
def main():
n = int(input())
a = list(map(int, input().split()))
print(Solution().canPartition(a))
if __name__ == "__main__":
main()复杂度
- 时间复杂度:
。 - 空间复杂度:
。
总结
分割等和子集是 0/1 背包的判定版本。倒序更新是关键:正序更新会导致同一元素被多次选取。