问题描述:
晓萌希望将1到N的连续整数组成的集合划分成两个子集合,且保证每个集合的数字和是相等。例如,对于N=3,对应的集合{1,2,3}能被划分成{3} 和 {1,2}两个子集合.
这两个子集合中元素分别的和是相等的。
对于N=3,我们只有一种划分方法,而对于N=7时,我们将有4种划分的方案。
输入包括一行,仅一个整数,表示N的值(1≤N≤39)。
输出包括一行,仅一个整数,晓萌可以划分对应N的集合的方案的个数。当没发划分时,输出0。
样例输入
7
样例输出
4
解题思路:
首先自定义点类,如代码所示,然后定义点类的二维数组p[40][781],动态规划的主要工作就是填写二维数组这张表。p[i][j]存放的是整数[0,i]的组成的和不大于j 的情况下的最大值value,所有情况的种类数kind。例如p[2][4]{value=3,kind=1}意思是,1,2组成最大值3的种类数为1;
另外,我们可以证明一组连续的自然数可以选出一组互异的元素组成任何不大于其总和的数(主要是程序中的一个语句段的解释);使用归纳法证明如下:对于假设[1,k]可以组成任何小于其和sum(k)的元素,则对于[1,k+1],其和为sum(k+1)=sum(k)+k+1;对于[sum(k)+1,sum(k+1)]期间的数可以分解为(n)+sum(k),其中1<=n<=k+1;对于sum(k)由归纳假设,sum(k)可以有[1,k]的一组互异元素构成;对于k+1,已知当k>2时,k+1<sum(k),k+1由其本身构成,则k+1与sum(k)的组合元素互异,可以得出该性质在k>2的情况下,由k时可以推出k+1时成立。对于k<=2的情况,k=1,时1(1)括号内为组成元素,k=2时,1(1),2(2),3(1,2);而k=3时(可以作为归纳证明的基本情况),1(1),2(2),3(1,2),4(1,3),5(2,3),
6(1,2,3)满足假设的性质。
对于本题的代码实现如下:
#include<iostream>
using namespace std;
class Point {
public:
int value;
long long kind;
Point(int value1=0,int kind1=0) :value(value1),kind(kind1){
}
};
long long getNum(int n) {
int sum = n*(1 + n) / 2;
if (sum % 2 == 1) {
return 0;
}
sum /= 2;
Point p[40][781];
for (int i = 1; i < 40; ++i) {//自然数和为0的种类数为1(方便处理);
p[i][0].kind=1;
p[i][0].value = 0;
p[i][1].kind = 1;
p[i][1].value = 1;
}
for (int j = 1; j < 781; ++j) {//自然数1组成大于等于1的种类为1,最大值为1;
p[1][j].kind = 1;
p[1][j].value = 1;
}
long long a,b;
for (int i = 2; i<= n; ++i) {
for (int j = 2; j <= sum; ++j) {
a = 0;
if (p[i - 1][j].value >= j) {//不包含自然数i时的种类数;
a= p[i - 1][j].kind;
}
b = 0;
if (j - i >= 0) {//包含自然数i时的种类数;
b = p[i - 1][j - i].kind;
}
p[i][j].kind = a + b;
p[i][j].value = p[i - 1][j].value+i;
if (p[i][j].value>=j) {//这里保证最大值不超过j;不必考虑是否有组合正好和为j;简单证明如分析所示。
p[i][j].value = j;
}
}
}
return p[n][sum].kind/2;
}
void input() {
int n;
cin >> n;
cout << getNum(n) << endl;
}