题解 | 第二题

#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,那么把这个元素单独成组即为最优解,不必继续递归。

全部评论

相关推荐

07-09 18:28
门头沟学院 Java
写着提前批,结果还要实习4个月以上???
程序员牛肉:这种不用看,直接投了,面试的时候问对应的HR就行。有可能他们是直接复制的暑期实习的模板。
点赞 评论 收藏
分享
屌丝逆袭咸鱼计划:心态摆好,man,晚点找早点找到最后都是为了提升自己好进正职,努力提升自己才是最关键的😤难道说现在找不到找的太晚了就炸了可以鸡鸡了吗😤早实习晚实习不都是为了以后多积累,大四学长有的秋招进的也不妨碍有的春招进,人生就这样
点赞 评论 收藏
分享
07-09 18:33
门头沟学院 Java
这么逆天每年都有人去???&nbsp;填多益网申就是大型的服从性测试
鲁大牛:辅导员在群里发了这个公司我就申了一下。网申居然要写当场开摄像头写两篇不少于三百字的作文。太逆天了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务