`
java-mans
  • 浏览: 11427395 次
文章分类
社区版块
存档分类
最新评论

【编程之美】数组分割问题

 
阅读更多

一,问题:

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

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

二,分析:

假设数组A[1..2N]所有元素的和是SUM。模仿动态规划解0-1背包问题的策略,令S(k, i)表示前k个元素中任意i个元素的和的集合。显然:
S(k, 1) = {A[i] | 1<= i <= k}
S(k, k) = {A[1]+A[2]+…+A[k]}
S(k, i) = S(k-1, i) U {A[k] + x | x属于S(k-1, i-1) }
按照这个递推公式来计算,最后找出集合S(2N, N)中与SUM最接近的那个和,这便是答案。这个算法的时间复杂度是O(2^N).
因为这个过程中只关注和不大于SUM/2的那个子数组的和。所以集合中重复的和以及大于SUM/2的和都是没有意义的。把这些没有意义的和剔除掉,剩下的有意义的和的个数最多就是SUM/2个。所以,我们不需要记录S(2N,N)中都有哪些和,只需要从SUM/2到1遍历一次,逐个询问这个值是不是在S(2N,N)中出现,第一个出现的值就是答案。我们的程序不需要按照上述递推公式计算每个集合,只需要为每个集合设一个标志数组,标记SUM/2到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. 解法:

但本题还增加了一个限制条件,即选出的物体数必须为n,这个条件限制了内阶段k2的取值范围,并且dp[k][s]的含义也发生变化。这里的dp[k][s]表示从前k个数中取任意不超过n的k个数,且这些数之和为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);
}


注意:如果数组中有负数的话,上面的背包策略就不能使用了(因为第三重循环中的s是作为数组的下标的,不能出现负数的),需要将数组中的所有数组都加上最小的那个负数的绝对值,将数组中的元素全部都增加一定的范围,全部转化为正数,然后再使用上面的背包策略就可以解决了。




分享到:
评论

相关推荐

    数组分割1

    3. #include "CreateArray.h" //该头文件是动态开辟及销毁二维三维数组的,读者自己实 4. using namespace std 1

    webgl编程指南及源码2/2

    文件太大,分割成两个文件了。 原书名:WebGL Programming Guide: Interactive 3D Graphics Programming with WebGL (OpenGL) 原出版社: Addison-Wesley Professional 作者: (美)Kouichi Matsuda Rodger Lea(松田...

    webgl编程指南及源码1/2

    文件太大,分割成两个文件了。 原书名:WebGL Programming Guide: Interactive 3D Graphics Programming with WebGL (OpenGL) 原出版社: Addison-Wesley Professional 作者: (美)Kouichi Matsuda Rodger Lea(松田...

    IOI国家集训队论文集1999-2019

    骆 骥 -《浅析解 "对策问题" 的两种思路——从《取石子》问题谈起》 孙方成 -《偶图的算法及应用》 孙林春 -《让我们做得更好——从《Parity》的解法谈程序的优化》 王知昆 -《搜索顺序的选择》 许智磊 -《二分...

    精易模块[源码] V5.15

    4、修正“文本_逐字分割”返回数组不清除会保留上次内容的问题,感谢易友【@JadeジYu】反馈。 5、新增“文本_是否为双字节字符”与OPenGL支持库-&gt;文字轮廓 中的 是否为双字节字符功能相同。 6、新增“文本_是否为...

    Shell脚本专家指南

    8.1 用数组实现进程树 8.2 用非直接变量实现进程树 8.3 用Bourneshell实现进程树 第9章 数据重定向 9.1 避免错误 9.2 普通重定向 9.3 访问用户指定的文件句柄 9.4 从shell中访问描述符 第10章 管道输入读 10.1 逐行...

    JAVA 范例大全 光盘 资源

    常见问题 for循环初始化问题 31 .第4章 数组 32 实例13 一维数组复制、插入和合并 32 实例14 数组排序 35 实例15 数组搜索 37 实例16 去掉数组重复数字 39 实例17 求质数(素数) 41 实例18 矩阵的加减和转置...

    PHP基础教程 是一个比较有价值的PHP新手教程!

    )可以被用来分割某些特殊字符。举例如下: $first = 'Hello'; $second = "World"; $full1 = "$first $second"; # 产生 Hello World $full2 = '$first $second';# 产生 $first $second 可以将字符和数字利用运算...

    Python 科学计算

    欢迎加入非盈利Python学习交流编程QQ群783462347,群里免费提供500+本Python书籍! VIII Python 科学计算 目 录 3.5.1 中值滤波.....................................97 3.5.2 滤波器设计............................

    1345个易语言模块

    ricky52529 编程超级模块.ec RUN++行++模块2.ec RUNONCE.EC runtime.ec RUN加减模块1.0+ 名.ec SAVEPIC.EC Sc千寻专用模块.ec SetIEProxy.ec setuser.ec sev.ec shell.ec SHELL32.EC ShutDown.ec ShutDown1.ec SH_...

    1350多个精品易语言模块

    ricky52529 编程超级模块.ec RUN++行++模块2.ec RUNONCE.EC runtime.ec RUN加减模块1.0+ 名.ec SAVEPIC.EC Sc千寻专用模块.ec SetIEProxy.ec setuser.ec sev.ec shell.ec SHELL32.EC ShutDown.ec ShutDown1.ec SH_...

    易语言模块大全(共775个模块)

    分割无逢文本模块(1.0).zip 分辨率模块(1.0).zip 方德算法-取CPU特征字(1.0).zip 仿WinXP窗口v1.0(1.0).zip 仿WinXP窗口v1.1(1.0).zip 复制、移动、建多目录模块(1.0).zip 广告(1.0).zip 改变显示器状态(1.0).zip ...

    易语言700模块打包

    分割无逢文本模块(1.0).zip 分辨率模块(1.0).zip 方德算法-取CPU特征字(1.0).zip 仿WinXP窗口v1.0(1.0).zip 仿WinXP窗口v1.1(1.0).zip 复制、移动、建多目录模块(1.0).zip 广告(1.0).zip 改变显示器状态(1.0)...

Global site tag (gtag.js) - Google Analytics