10.3.5统计n-k特殊集的数

描述:

如果由正整数构成的集合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;
}

 

 

猜你喜欢

转载自blog.csdn.net/weixin_42373330/article/details/82889511
n^k