题目信息
对于由从1到N (1 <= N <= 39)
这N个连续的整数组成的集合来说,我们有时可以将集合分成两个部分和相同的子集合。
例如,N=3
时,可以将集合{1, 2, 3}
分为{1,2}
和{3}
。此时称有一种方式(即与顺序无关)。
N=7
时,共有四种方式可以将集合{1, 2, 3, ..., 7}
分为两个部分和相同的子集合:
{1,6,7}
和{2,3,4,5}
{2,5,7}
和{1,3,4,6}
{3,4,7}
和{1,2,5,6}
{1,2,4,7}
和{3,5,6}
输入
程序从标准输入读入数据,只有一组测试用例。如上所述的N。
输出
方式数。若不存在这样的拆分,则输出0。
测试样例
7
4
解答
#include <iostream>
using namespace std;
int dp[40][400];
int main()
{
//freopen("E://test.txt", "r", stdin);
int N;
cin >> N;
int sum = (N + 1) * N / 4;
if ((N + 1) * N % 4 != 0)
{
cout << 0 << endl;
}
else
{
dp[0][0] = 1;
for (int i = 1; i <= N; i++)
{
for (int j = 0; j <= sum; j++)
{
if (j >= i)
{
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - i];
}
else
{
dp[i][j] = dp[i - 1][j];
}
}
}
cout << dp[N][sum] / 2 << endl;
}
return 0;
}
优化的DP
#include <iostream>
using namespace std;
int dp[400];
int main()
{
//freopen("E://test.txt", "r", stdin);
int N;
cin >> N;
int sum = (N + 1) * N / 4;
if ((N + 1) * N % 4 != 0)
{
cout << 0 << endl;
}
else
{
dp[0] = 1;
for (int i = 1; i <= N; i++)
{
for (int j = sum; j >= 0; j--)
{
dp[j] += dp[j - i];
}
}
cout << dp[sum] / 2 << endl;
}
return 0;
}
想法
题目的意思是可以将数组分为两堆,且两堆的和相等。
由于是从1到N求和,所以所有数字的和为(1 + n) * n / 2
,那么一边的数字和为再除2:(1 + n) * n / 4
,但是如果再除2的时候如果无法除尽的话意味着无法平分,直接输出0就可以了。
给定集合S={1,2,3...,N}
,我们将其一字排开,挨个判断每个数是否应该加入到满足整数和为N*(N+1)/4
的子集合C。对于每个数,要么可以被加入到集合C,要么不可以被加入,只有这两种可能。那么如何知道当前的整数是否应该加入子集合呢?由于这个子集合的和与单个整数大小有悬殊,我们似乎一眼看不出来。既然这样,我们不妨缩小问题规模来渐进考虑,而缩小问题规模的常见切入点是减小“自变量”的规模。
重读“接下来…”那句话,发现其中有两个条件,一个目的。目的是“有多少种选取方案”,这就是因变量。条件是“选出k个数”和“使其和为N(N+1)/4”,这就是两个自变量,一个限制选取的整数,另一个限制选出的整数的和。既然有了自变量和因变量,不如定义个函数出来更好的描述问题:F(i, sum)
。在函数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, 0) = 1
,明白了此点,也就顺利地得出:F(0, 1)=F(0, 2)=F(0, 3)...=F(0, N)=0
,因为限制子集合整数和大于0时,光有0无论如何也不能选出符合此限制的整数集合,即可行方案数为0。
假设此时,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)
。