描述:
如果由正整数构成的集合X满足以下条件,我们称它为n-k特殊集:
(1)集合X中的每个元素x均不超过n,即1<=x<=n.
(2)集合X中所有元素之和大于k。
(3)集合X中不包含任意一对相邻的自然数。
给出n,k,求n-k特殊集合有多少个。1<=n<=100,0<=k<=400.
样例输入:6 3
输出:17
样例输入:14 55
输出:1
分析:
设n-k的特殊集的个数为f(n,k),我们来想办法建立它的递推式。由于集合中的元素不能重复,元素n要么在集合中恰好出现一次,要么不重复,不遗漏地把n-k特殊集分成了两部分:
(1)n不出现。除了规则1中的n需要修改为n-1外,其他均不变,因此有f(n-1,k)个。
(2)n出现一次。规则1中的n需要修改为n-2(如果出现了n-1,则相邻的自然数n-1,n不满足条件),而规则2中的k应变为k-n.
换句话说,递推关系是:f(n,k)=f(n-1,k)+f(n-2,k-n)。那么边界应该是什么呢?n<=0时只有空集一个集合满足规则1,而此时所有元素和为0,因此:
□当n<=0,k>=0时,f(0,k)=0.
□当n<=0,k<0时,f(0,k)=1.
在其他情况下,f均能按此递推式计算。但这样一来,问题就来了:如果用f[n][k]保存f(n,k)的值,n和k需要允许负数!
第一种处理方法是动态判断边界,而不是在初始化时一口气给全部边界赋上值。这样的话,上面的边界就不够了:尽管f(5,3)也可以按照递推式计算,但为了方便程序编写,我们直接把f(n,k)(k<0)作为边界。可是k<0是,f(n,k)应该等于多少呢?此时k已经不重要了,设g(n)=f(n,-1),则可以仿照刚才的推理写出如下递推式:g(n)=g(n-1)+g(n-2),边界g(-1)=g(0)=1.这正是Fibonacci数列!
这种方法很通用,但琐碎,不直观,而且还不得不借助一个辅助函数g。没关系,我们用另一种处理方法。既然k<0时f(n,k)与k无关,用-1来“代表”所有的负数。这样边界就只剩两个了:f(n,k)=0(n=0,-1,k>=0),f(n,-1)=1(n=0,-1).
#include <iostream>
#define F(i,j) (f[(i)+2][(j)+1]) //用宏定义支持负数下标
using namespace std;
int f[200][500]; //结果可能很大,需要使用高精度类bign
int main()
{
int i, j, n, k;
cin>>n>>k;
for(j=-1; j<=k; j++) //边界
F(-1,j)=F(0,j)=0;
F(-1,-1)=F(0,-1)=1;
for(i=1;i<=n;i++)
for(j=-1; j<=k; j++) //递推
{
F(i,j)=F(i-1,j);
if(j-i<0) F(i,j)+=F(i-2,-1);
else F(i,j)+=F(i-2,j-i);
}
cout<<F(n,k)<<"\n";
return 0;
}