- 基础问题
分割等和子集
- 2025-8-28 23:48:18 @
题意概括
给定一个只包含正整数的非空数组,判断是否能将这个数组分割成两个子集,使得这两个子集的元素和相等。
数据范围:
- 数组中每个元素的取值范围为 到 。
基础知识
这个问题是经典的 0/1 背包问题 的一个变种。
首先,我们可以计算出数组所有元素的总和 。如果我们要将数组分成两个和相等的子集,那么每个子集的和都必须是 。
- 如果总和 是一个奇数,那么它不可能被平分成两个整数和的子集,因此可以直接判断为
false
。 - 如果总和 是一个偶数,问题就转化为:能否从数组中挑选出若干个元素,使得这些元素的和恰好等于 。
这就是一个标准的0/1背包问题:我们有一个容量为 的背包,和 个物品(数组中的每个数字),每个物品的“重量”和“价值”都是它自身的数值。我们需要判断是否能恰好装满这个背包。
我们可以使用动态规划来解决。定义一个布尔数组 ,其中 表示是否可以用数组中的数字凑成和为 。
- 状态定义: 是一个布尔值,
true
表示可以从数组中选出子集使得和为 ,false
表示不可以。 - 状态初始化: ,因为不选任何数,和为0,这是可行的。
- 状态转移: 我们遍历数组中的每一个数字 。对于每个数字 ,我们更新 数组。为了凑成新的和 ,我们可以考虑是否使用 。如果在使用 之前,我们已经能凑成 (即 为
true
),那么加上 之后我们就能凑成 。状态转移方程为: 为了保证每个数字只被使用一次(0/1背包),我们需要对 进行逆序遍历(从 到 )。
最终,我们只需要检查 的值即可。如果为 true
,则说明可以找到和为 的子集,原问题有解。
该算法的时间复杂度为 ,空间复杂度为 。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
void run() {
int n;
cin >> n;
vector<int> a(n);
int sum = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
sum += a[i];
}
// 如果总和为奇数,不可能平分
if (sum % 2 != 0) {
cout << "false" << endl;
return;
}
int tar = sum / 2;
vector<bool> dp(tar + 1, false);
// dp[0] 表示和为0是可达的(不选任何元素)
dp[0] = true;
// 0/1 背包过程
for (int x : a) {
// 必须倒序遍历,防止一个物品被多次使用
for (int j = tar; j >= x; --j) {
dp[j] = dp[j] || dp[j - x];
}
}
if (dp[tar]) {
cout << "true" << endl;
} else {
cout << "false" << endl;
}
}
int main() {
// 关闭同步流
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
run();
return 0;
}
/*
样例 1:
输入:
4
1 5 11 5
输出:
true
样例 2:
输入:
4
1 2 3 5
输出:
false
*/
0 条评论
目前还没有评论...