题解 | 第二题
#include<iostream>
#include<vector>
#include<string.h>// getline头文件
#include<math.h>
using namespace std;
int Stoi(char* begin) // 如果不合法返回-1,合法返回0
{
int sum = 0;
while (begin != NULL && *begin != ' ' && *begin != '\0') {
sum *= 10;
if (*begin <= '9' && *begin >= '0') {
sum += *begin - '0';
}
else {
return -1;
}
begin++;
}
return sum;
}
int sum;// 数组总和
int half;// 数组总和的一半
int vector_length;// 数组的总长度
int String2Vector(string& buf, vector<int>& BufV) // 如果不合法返回-1,合法返回0
{
char del = ' ';
char* token = strtok((char*)buf.c_str(), &del);
while (token != NULL) {
int cur = Stoi(token);
if (cur == -1) {
return -1;
}
BufV.push_back(cur);
token = strtok(NULL, &del);
}
sum = 0;
for (auto i : BufV) {
sum += i;
}
half = sum / 2;
vector_length = BufV.size();
return 0;
}
int best;// 记录分割之差最小所划分出来的子数组的和
bool fin_flag;// 如果找到分割之差最小的方法,递归可以提前结束
void dfs(vector<int>& BufV, int index, int value) {
if (fin_flag || (index == vector_length)) {// 递归结束
return;
}
if (value == half) { // 剪枝,如果已经达到分割之差最小
best = half;
fin_flag = true;
return;
}
// 如果正好达到half则不再继续递归。已经使分割之差最小
int cur = BufV[index];
if (cur >= half) {
best = cur;
fin_flag = true;
return;
}
// 如果有一个超出half的单个元素,则剪枝单独成组,不必再递归
int remain = value + cur;// 如果保留当前元素,则本次递归下去的和就是value + cur
int abandon = value;// 不保留当前元素则为value值
if (abs(half - remain) < abs(half - best)) {
best = remain;
}
else if (abs(half - abandon) < abs(half - best)){
best = abandon;
}
dfs(BufV, index + 1, remain);
dfs(BufV, index + 1, abandon);
// 分成两条递归路径,一条保留一条舍弃
}
int main()
{
string buf;
while (getline(cin, buf)) {
vector<int> BufV;
int flag = String2Vector(buf, BufV);
if (flag == -1) {
cout << "ERROR" << endl;
}
else {
fin_flag = false;
dfs(BufV, 0, 0);
int outputa = best;
int outputb = sum - best;
if (outputa < outputb)
swap(outputa, outputb);
cout << outputa << " " << outputb << endl;
}
}
return 0;
}
两种做法:一种按照传统dp思路做,另一种是按照暴力搜索+剪枝
传统dp:既然要将数组分成两组,并且要求两组和之差最小,首先可以想到,两个数组越接近总和的一半,差就越小。
可以看作是背包问题,总和的一半视为背包容量上限。注意一个细节,sum/2可能取整损失精度,但没关系啊,我们凑比较小的一组子数组即可,使其尽可能接近sum/2。因为在sum为奇数的情况下,无论怎么划分,都会有一组和更小,所以求出和最接近sum的子数组即可满足条件,不会有更优化分割法。状态转移方程: dp[i][cap] = max{dp[i - 1][cap], dp[i - 1][cap - cur] + cur}
缺陷在于,sum/2可能非常大,像题目中给出的几组数据,元素个数并不多,而总和很大,导致用于制表的空间很大,超出题目内存限制。
暴力搜索+剪枝:一个元素只有保留与舍弃两种可能,对于数组中每一个元素,计算保留与舍弃两种结果,比较哪个与sum/2更接近即可,然后递归到next元素。这样有2^n中可能,会超时,所以需要剪枝。一旦和已经等于sum/2,便不需要递归了,已经求出最优解。如果有一个元素自身超过sum/2,那么把这个元素单独成组即为最优解,不必继续递归。
科大讯飞公司氛围 474人发布