拆分集合为相等的子集合(第1届第1题)

题目要求

问题描述:将1到N的连续整数组成的集合划分为两个子集合,且保证每个集合的数字和相等。例如,对于N=4,对应的集合{1,2,3,4},能被划分为{1,4}、{2,3}两个集合,使得1+4=2+3,且划分方案只有此一种。编程实现给定任一正整数N(1<=N<=39),输出其符合题意的划分方案数。

样例输入1:3

样例输出1:1    (可划分为{1,2}、{3})

样例输入2:4

样例输出2:1    (可划分为{1,3}、{2,4})

样例输入3:7

样例输出3:4    (可划分为{1,6,7}、{2,3,4,5},或{1,2,4,7}、{3,5,6},或{1,3,4,6}、{2,5,7},或{1,2,5,6}、{3,4,7})

解决方案

此题的解决方案有多种,但基本思想是动态规划

首先,观察子集合的和。

对于任一正整数N,集合{1,2,3...N}的和为:

那么将集合S划分为两个和相等的子集合后,其子集合C中的整数和必为:

例如对于正整数4,集合{1,2,3,4}的和为S=4*(4+1)/2=10,那么将其划分为和相等的两个子集合后,其子集合C中的整数和为sum=S/2=5。

于是,此题就转化为在集合{1,2,3...N}中,任意选取k个数,使其和为N*(N+1)/4的问题,换句话说,就是限制子集合和为N(N+1)/4时,集合{1,2,3...,N}中可提供的整数选取方案数

此描述隐含两个条件:第一,N*(N+1)除以4必须为整数,否则无法选取;第二,在选出k个数的所有方案中,每个方案都有其互补的方案。还是拿整数4举例,对于集合{1,2,3,4},任选k个数,使其和为4*(4+1)/4=5时,有两种方案{1,4}和{2,3},这两种方案互补构成所有整数集合,并生成一种集合划分方案。由此可得出,将选取k个数的所有方案数除以2,就是集合N划分为相等的子集合的方案数。

接下来,从集合S中往出选k个数,使其和为N*(N+1)/4,看有多少种选取方案。

给定集合S={1,2,3...,N},我们将其一字排开,挨个判断每个数是否应该加入到满足整数和为N*(N+1)/4的子集合C。对于每个数,要么可以被加入到集合C,要么不可以被加入,只有这两种可能。那么如何知道当前的整数是否应该加入子集合呢?由于这个子集合的和与单个整数大小有悬殊,我们似乎一眼看不出来。既然这样,我们不妨缩小问题规模来渐进考虑,而缩小问题规模的常见切入点是减小“自变量”的规模

重读“接下来...”那句话,发现其中有两个条件,一个目的。目的是“有多少种选取方案”,这就是因变量。条件是“选出k个数”和“使其和为N(N+1)/4”,这就是两个自变量,一个限制选取的整数,另一个限制选出的整数的和。既然有了自变量和因变量,不如定义个函数出来更好的描述问题:

在函数F(i, sum)中,i代表当前需要判断集合S中第i个数是否应该加入子集合sum代表此时限制的子集合整数和大小F(i, sum)代表限制子集合整数和为sum时,集合{0,1,2...,i-1,i}中可提供的选取方案。(如果有点蒙,继续往下看,后面会附图...事实证明多看几遍就理解了...)

如果我们要减小自变量规模,就要从上面两个自变量下手。先看选取的整数,集合S中的最小整数是1,我们加入一个更小的整数0(显然,0不会影响集合的划分)来辅助思考。对于选出的整数的和,也就是限制的子集合的整数和,我们也从最小的0开始判断,那么问题的最小规模就是:限制子集合的整数和为0时,考虑整数0是否可以加入子集合?这个问题的答案是肯定的,当子集合的和为0时,完全可以将0加入,那么用上述函数来表达就是:

明白了此点,也就顺利地得出:F(0, 1)=F(0, 2)=F(0, 3)...=F(0, N)=0,因为限制子集合整数和大于0时,光有0无论如何也不能选出符合此限制的整数集合,即可行方案数为0。

迈出第一步,后面的就好办了...这是安慰人,事实是前方高能,更费心神!

为避免词语重复,下面说S中第i个元素时,就是指第i个整数。

假设此时,S中前i-1个元素都判断完了,紧接着应该判断第i个元素,与此同时,子集合的整数和被限定为sum,那么这第i个元素要不要被加入子集合呢?对此,我们做如下推断:

1:如果这第i个元素本身大于子集合的整数和sum,即i>sum,那么这第i个元素肯定不能加入子集合,否则就超出子集合整数和限制了。此时:F(i, sum)=F(i-1, sum),意思就是在相等的子集合整数和限制下,既然第i个元素没被加入,那么判断完第i个元素后的整数选取方案与判断完第i-1个数时的方案应该是相同的。

2:如果这第i个元素小于子集合整数和,那么就有两种考虑:

2.1:坚持不把第i个元素放入子集合,那么此时整数的选取方案仍然有F(i-1, sum)种。

2.2:如果把第i个元素放入了子集合,那么此时整数的选取方案有F(i-1, sum-i)种,sum-i的含义在于既然要放入第i个元素,就要给它留下足够的空间。F(i-1, sum-i)是在肯定要放入元素i的情形下,放入元素i前,整数的选取方案。

也即是说,i<=sum时,F(i, sum)=F(i-1, sum)+F(i-1, sum-i)

综上可得

如果觉得这个式子还是比较蒙圈,那还是从具体的解决方案入手深化理解,毕竟理论都是抽象的,不好琢磨。

下面的解决方案中,我们均设定N=4,那么集合S={1,2,3,4}对应的最终子集合的整数和就是4*(4+1)/4=5,即求F(4, 5)的值。

解决方案一

先对可能出现的图例做说明:

图零:

当对第0个元素判断时:

>若子集合整数和限定为0,那么只有把0放入子集合这一种可能。若子集合整数和大于0,那么放入0显然不能满足题意,故其选取方案均为0。

图一:

当对第1个元素判断时:

>限定子集合整数和为1:若要将元素1放入子集合,则1之前子集合中的元素和必须为1-1=0;若不放入元素1,则1之前子集合中的元素和必须为1,故在此子集合整数和限制下,加入1和不加入1就组成了两种方案,且这两种方案数的和为:F[1,1]=F[0,0]+F[0,1]

>限定子集合整数和为2:若要将元素1放入子集合,则1之前子集合中的元素和必须为2-1=1;若不放入元素1,则1之前子集合中的元素和必须为2,即:F[1,2]=F[0,1]+F[0,2]

>依次类推F[1,3]=F[0,2]+F[0,3]F[1,4]=F[0,3]+F[0,4]F[1,5]=F[0,4]+F[0,5]

>注意最后:当选取的元素i(纵向)大于子集合整数和(横向)时,F[i, sum]=F[i-1, sum];也就是说,此时的元素i肯定放不进子集合,那么它满足题意的选取方案与上一个元素的方案一致。

       图二:

       图三:

       图四:

最后,右下角的值F[i, sum]反应了所有满足题意的子集合数,将其除以2才是集合S的划分方案数。

源码示例一

解决方案二

由上面的解释,不知道大家是否察觉到计算过程其实就是个递归过程,那么我们尝试将其转换为递归形式。

结合综述中的式子,递归应该是最好被理解的,但是递归的缺点就是计算太慢...

源码示例二

解决方案三

针对前面的叙述,换一个角度思考。

给定集合{0,1,2,3,4},如果我们是按顺序挑选的,那么要使选出的元素和为5,那么可以是选出元素和为5的组合,再把元素0加进来(如果之前的组合中没有0),还可以先选出和为4的元素组合,再把元素1加进来(如果之前的组合中没有1),或者,可以先选出和为3的元素组合,再把元素2加进来(如果之前的组合中没有2),再或者,可以先选出和为2的元素组合,再把元素3加进来(如果之前的组合中没有3)...最后,还可以是先选出和为0的组合,然后再把元素5加进来(如果之前的组合中没有5)。

如果用S(sum)表示元素和为sum的一个组合,那么上面的叙述可表示为:

S(5)=S(5)+0;    S(5)=S(4)+1;    S(5)=S(3)+2;    S(5)=S(2)+3;    S(5)=S(1)+4;    S(5)=S(0)+5;

现在,再换一个维度考虑。

仍然是集合{0,1,2,3,4},如果我们按顺序挑选到了i,那么i可能成为S(0)到S(5)任一组合中的元素之一。

如果i成了S(5)中的一份子,那么S(5)的组成方案数必定是没加入i前S(5)已有的组成方案数加上加入i后S(4)的组成方案数。(定一定神,结合解决方案一考虑,每遍历到一个元素i,都要加上之前遍历过程中求出的解决方案数)。

下面上图...

图零:

当挑选到第0个元素时,显然,构成S[0]只有一种方案,就是把0放入,其他S[1]S[5]均为0。

图一:

当挑选到第1个元素时,满足S[5]的方案数等于当前已有的方案数(没加入元素1之前)加上满足S[4]的方案数(加上元素1),依次类推。

这里可能有两个疑问,第一是当前已有的方案数(没加入元素1)从何而来?事实上,这个方案数自初始化一来,就一直"遗传"下去,并在遍历到每个元素时,进行更新。另一个疑问是这里为什么倒着计算,即每遍历到一个元素,先从S[5]计算,其实是S[4...1]。这个原因在于每次更新数据前,当前位置保持的是遍历完上一个元素后的方案数,而计算当前遍历元素下的方案数时,总是需要用到遍历完上一个元素后的数据,所以,如果正着往后算,会造成数据错乱。(不知道我说清楚了没...)

举例,假如刚刚遍历完元素0,现在轮到遍历元素1了,此时上图数组的初始状态分别存储了遍历完元素0后满足子集合整数和为0、1、2、3、4、5的元素选取方案数。这个初始状态来自于上个元素,而且要被复用,所以必须等使用完了才能再根据当前元素1的情形进行更新。由于其复用的规律是后面用到前面的数据,所以从后往前推算就不会造成混乱了。

图二:

图三:

       图四:

源码示例三

结果展示

小结

好了,再说下去我也快蒙圈了...

动态规划题型很多,需大量练习才能领会。其核心思想就是计算后面的结果时,利用之前的结果。当不能一眼看出题目中的递推关系时,不妨先找到题目的自变量去减小题目规模来逐步考虑,在考虑时,注意特殊情况的处理。

另外,将文字叙述变为公式推导也是重要的技能,唯有多练才可以掌握。

刚接触动态规划的同学可以从0-1背包问题看起,这里有篇文章或许能给你带来启发:0-1背包问题和部分背包问题分析

时间: 2024-10-21 16:42:50

拆分集合为相等的子集合(第1届第1题)的相关文章

LeetCode 90. Subsets II (子集合之二)

Given a collection of integers that might contain duplicates, nums, return all possible subsets. Note: The solution set must not contain duplicate subsets. For example,If nums = [1,2,2], a solution is: [ [2], [1], [1,2,2], [2,2], [1,2], [] ] 题目标签:Arr

文本挖掘之文本推荐(子集合生成)

刘 勇   Email:[email protected] 简介 在研究文本推荐算法时,需要挖掘关键字之间的规则,其中比较重要的一步是构建关键字的集合,即需要求取一个集合的所有子集.因此本文根据需求,采用3种方式实现该算法,以期对后续算法研究提供帮助. 本文下面从二叉树递归.位图和集合3个角度,对该算法进行研究与实现. 二叉树递归 为简要描述,采用数据集为:A= {1,2,3}.二叉树递归如下图-1所示. 图-1 二叉树递归过程 对图-1采用二叉树递归解释为:集合中每个元素有2中状态,属于某个集

蓝桥杯——分治法之子集合的个数

{1,2,3}子集合的个数,有{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3},求n个元素组成的集合,m个元素组成的子集合的个数. 如上例,3个元素由其中2个组成的子集合有3个. public class Main{ public static void main(String[] args) { // TODO Auto-generated method stub System.out.print(cal(3, 2)); } static int cal(int n,

0-1背包问题与子集合加总问题的近似算法

最近没有怎么更新博客,因为一直比较忙.最近发现所里在做的一个项目中,可以抽出一部分内容和0-1背包问题.子集合加总问题非常相似(虽然表面上不容易看出相似点),所以看了一些这方面的资料和论文,这里主要对问题特点和算法思想做一些整理.这类问题其实很有意思,做数学和做计算机的人都会研究,而且我这里将要提到的论文都是做计算机的人所写的. 问题简述0-1 Knapsack Problem (0-1背包问题,下面简称KP)和Subset Sum Problem (子集合加总问题,下面简称SSP)是经典的NP

HLJU 1105 cpc 喵喵的拆分集合 (并查集的逆向操作)

1105: 喵喵的拆分集合 Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 37  Solved: 8 [Submit][Status][Web Board] Description 众所周知,DoubleQ是DS(Data Structure)粉,她最爱DS了.现在她要实现一个神奇的DS,支持下列两个操作: -删除某条边,表示为"D x",即为删除第x条边 -查询两点是否属于一个集合,表示为"Q a b",即为查询节

如何去掉子集合功能中的按钮?

解决方案:1.找到子集合字段2.打开字段详细信息,在辅助配置里面进行配置 加入JEPaaS技术交流群,了解更多** 原文地址:https://blog.51cto.com/13797782/2440352

Collection集合中各子集合的差别

一.HashMap和HashTable的差别 1.HashMap能允许一个null key和多个null value值:HashTable不允许为null: 2.HashMap不同步非安全:HashTable是同步的,线程安全. 3.两者加载因子都是0.75:但是扩容增量存在差别:HashMap为1倍,HashTable为2倍+1: 二.ArrayList.Vector.LinkedList差别 1.ArrayList与Vector的差别 底层都是数组结构: Vector扩容是2倍:ArrayL

集合计划客家话分分合合

http://www.l99.com/EditVideo_view.action?videoId=788912 http://www.l99.com/EditVideo_view.action?videoId=788912 http://www.l99.com/EditVideo_view.action?videoId=788912 http://www.l99.com/EditVideo_view.action?videoId=788912 http://www.l99.com/EditVid

切分 拆分集合list的方式

一般有两种,第一是sublist(),代码冗余效率低: 第二种: 引包自 com.google.common.collect.Lists 话不多说直接上实例: List<ContractModel> contractList = ****; List<List<ContractModel>> partition = Lists.partition(contractList, Constants.BATCH_SIZE);for (List<ContractModel