1. 程式人生 > >10.3.5統計n-k特殊集的數

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;
}