编程之美2.18——数组分割

本文探讨了如何将一个无序的正整数数组分割成两个子数组,使它们的和尽可能接近。提供了两种情况下的解决方案:不限制子数组元素数量及限定每个子数组含有n个元素的情况。

问题:

1. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为两个子数组,子数组的元素个数不限,并使两个子数组之和最接近。

2. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组之和最接近。


1. 解法1:

由于对两个子数组和最接近的判断不太直观,我们需要对题目进行适当转化。我们知道当一个子数组之和最接近原数组之和sum的一半时,两个子数组之和是最接近的。所以转化后的题目是:从2n个数中选出任意个数,其和尽量接近于给定值sum/2。


这个问题存储的是从前k个数中选取任意个数,且其和为s的取法是否存在dp[k][s]。之所以将选出的数之和放在下标中,而不是作为dp[k]的值,是因为那种做法不满足动态规划的前提——最优化原理,假设我们找到最优解有k个数p1p2...pk(选出的这k个数之和是最接近sum/2的),但最优解的前k-1个数p1p2...pk-1之和可能并不是最接近sum/2的,也就是说可能在访问到pk之前有另一组数q1q2....qk-1其和相比p1p2...pk-1之和会更接近sum/2,即最优解的子问题并不是最优的,所以不满足最优化原理。因此我们需要将dp[k]的值作为下标存储起来,将这个最优问题转化为判定问题,用带动态规划的思想的递推法来解。


外阶段:在前k1个数中进行选择,k1=1,2...2*n。
内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。

状态:这k2个数的和为s,s=1,2...sum/2。

决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。
dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。

#include <iostream>
#include <algorithm>

using namespace std;

#define MAXN 101
#define MAXSUM 100000
int A[MAXN];
bool dp[MAXN][MAXSUM];

// dp[k][s]表示从前k个数中去任意个数,且这些数之和为s的取法是否存在
int main()
{
	int n, i, k1, k2, s, u;
	cin >> n;
	for (i=1; i<=2*n; i++)
		cin >> A[i];
	int sum = 0;
	for (i=1; i<=2*n; i++)
		sum += A[i];
	memset(dp,0,sizeof(dp));
	dp[0][0]=true;
	// 外阶段k1表示第k1个数,内阶段k2表示选取数的个数
	for (k1=1; k1<=2*n; k1++)			// 外阶段k1
	{
		for (k2=k1; k2>=1; k2--)		// 内阶段k2
			for (s=1; s<=sum/2; s++)	// 状态s
			{
				//dp[k1][s] = dp[k1-1][s];
				// 有两个决策包含或不包含元素k1
				if (s>=A[k1] && dp[k2-1][s-A[k1]])
					dp[k2][s] = true;
			}
	}
	// 之前的dp[k][s]表示从前k个数中取任意k个数,经过下面的步骤后
	// 即表示从前k个数中取任意个数
	for (k1=2; k1<=2*n; k1++)
		for (s=1; s<=sum/2; s++)
			if (dp[k1-1][s]) dp[k1][s]=true;
	// 确定最接近的给定值sum/2的和
	for (s=sum/2; s>=1 && !dp[2*n][s]; s--);
	printf("the differece between two sub array is %d\n", sum-2*s);
}

解法2:

由于题目不限制子数组的元素个数,限制条件少,可以进行优化。实际上解法1的思路主要是为了题目2做铺垫,使得题目2的解法不至于太难理解。该题实际上有更简单的解法,该解法的思路和0-1背包问题的思路是一样的。

#include <iostream>
using namespace std;

#define MAXN 101
#define MAXSUM 100000
int A[MAXN];
bool dp[MAXN][MAXSUM];

// dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在
int main()
{
	int k, s, u, i, n;
	cin >> n;
	for (i=1; i<=2*n; ++i)
		cin >> A[i];
	int sum = 0;
	for (i=1; i<=2*n; ++i)
		sum += A[i];
	dp[0][0] = true;
	// 阶段k表示第k个数
	for (k=1; k<=2*n; ++k)
		// 注意状态可取0
		for (s=0; s<=(sum>>1); ++s)
		{
			// 加上第k个数,或不加它所能得到的和
			if (s>=A[k])
				dp[k][s] = dp[k-1][s-A[k]] || dp[k-1][s];
			else
				dp[k][s] = dp[k-1][s];
		}
	for (s=(sum>>1); s>=1 && !dp[2*n][s]; --s);
	cout << sum-2*s;
}
 


2. 解法:

但本题还增加了一个限制条件,即选出的物体数必须为n,这个条件限制了内阶段k2的取值范围,并且dp[k][s]的含义也发生变化。这里的dp[k][s]表示从前k个数中取k个数,且k不超过n,且这些数之和为s的取法是否存在。

#include <iostream>
#include <algorithm>

using namespace std;

#define MAXN 101
#define MAXSUM 100000
int A[MAXN];
bool dp[MAXN][MAXSUM];

// 题目可转换为从2n个数中选出n个数,其和尽量接近于给定值sum/2
int main()
{
	int n, i, k1, k2, s, u;
	cin >> n;
	for (i=1; i<=2*n; i++)
		cin >> A[i];
	int sum = 0;
	for (i=1; i<=2*n; i++)
		sum += A[i];
	memset(dp,0,sizeof(dp));
	dp[0][0]=true;
	// 对于dp[k][s]要进行u次决策,由于阶段k的选择受到决策的限制,
	// 这里决策选择不允许重复,但阶段可以重复,比较特别
	for (k1=1; k1<=2*n; k1++)				// 外阶段k1
		for (k2=min(k1,n); k2>=1; k2--)		// 内阶段k2
			for (s=1; s<=sum/2; s++)	// 状态s
				// 有两个决策包含或不包含元素k1
				if (s>=A[k1] && dp[k2-1][s-A[k1]])
					dp[k2][s] = true;
	// 确定最接近的给定值sum/2的和
	for (s=sum/2; s>=1 && !dp[n][s]; s--);
	printf("the differece between two sub array is %d\n", sum-2*s);
}

### 关于北大青鸟消防编程 V2.18 版本功能说明及教程 根据已知的信息,北大青鸟消防设备及其相关软件在行业内具有较高的知名度。然而,在提供的引用中并未明确提及具体版本号为 **V2.18** 的消防编程软件或其功能说明文档[^1]。 通常情况下,此类软件的功能说明教程可以通过官方渠道获取。以下是可能的解决方案: #### 官方资源 - 可尝试访问北大青鸟官方网站或其他授权的技术支持页面寻找对应版本的资料。 - 提供的链接 `https://gitcode.com/Open-source-documentation-tutorial/cbe7f` 中可能存在与北大青鸟编程相关的技术文档,建议进一步查阅是否有针对 V2.18 的描述[^1]。 #### 软件特性推测 基于现有引用中的信息,可以合理推断北大青鸟消防编程软件的主要用途包括但不限于: - 实现对消防设备电源系统的监控并报告异常情况[^2]。 - 支持通过多线控制盘实现手动或自动操作模式下的设备启停管理[^3]。 - 配合火灾自动报警系统完成联动逻辑设置,从而提升整体消防安全性能[^4]。 对于具体的 **V2.18** 版本而言,由于缺乏直接关联的数据源,无法确切给出详细的更新日志或者新增特性的介绍。一般新版本可能会优化界面交互体验、增强稳定性以及增加一些高级配置选项来满足更复杂场景的应用需求。 如果确实需要该特定版次的教学材料,则除了前述提到的方式之外还可以考虑联系厂商客服团队请求帮助;另外也可以加入一些专注于此领域交流的学习社群分享经验心得互相解答疑惑之处。 ```python # 示例代码片段展示如何模拟查询远程API接口以检索指定版本信息(假设存在这样的服务端口) import requests def fetch_version_details(version_number): api_url = f"http://exampleapi.nbdqbirdfirecontrolsoftwareversions/{version_number}" response = requests.get(api_url) if response.status_code == 200: return response.json() else: raise Exception(f"Failed to retrieve details for version {version_number}") try: result = fetch_version_details('v2.18') print(result['features']) except Exception as e: print(e) ``` 以上脚本仅为示意目的编写,并不代表实际可用的服务路径,请替换真实的URL地址后再执行测试验证过程。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值