1. 程式人生 > >【演算法】幾個經典遞推關係記錄

【演算法】幾個經典遞推關係記錄

遞推關係數學味很重(貌似很多都是直接有公式的),不過很多記憶搜尋或者dp的狀態轉移都是要自己推理的,記錄幾個俗爆炸的遞推關係...

文科生學計算機......我呵呵一臉 :(

Fibonacci

這個很常見,第一次刷題愣是沒找出來...覺得特難(文科生的悲哀)!

目前見過的有 : 兔子生兔子(竟然全是母的還都有孕)母牛生小牛 ; 超級樓梯(一次爬一階或者兩階) ; 蜜蜂的巢等

總之模型大概是, 現有的 = 已有的+新生的(不一定是f(n-1)和f(n-2))

#include<cstdio>
long long a[50];
int main()
{
    int n;
    a[1]=a[2]=1;
    scanf("%d",&n);
    for(int i=3;i<=n;i++)
        a[i]=a[i-1]+a[i-2];
    for(int i=1;i<=n;i++)
        printf("%lld  ",a[i]);
    return 0;
}

Catalan數

最經典的應該是劃分凸多邊形吧: 固定點n和點1 , 利用k的位置進行分類(2<=k<=n-1),左邊是凸k邊形 右邊是凸n-k+1邊形

順推求解(有點記憶化的味道)

#include<cstdio>
#include<cmath>
using namespace std;
long long a[50];
int main()
{
    int n;
    a[2]=a[3]=1;
    scanf("%d",&n);
    for(int i=4;i<=n;i++)
        for(int k=2;k<=i-1;k++)
            a[i]+=a[i-k+1]*a[k];
    for(int i=3;i<=n;i++)
        printf("%lld  ",a[i]);
    return 0;
}

危險的組合

這個太有名了...

對於n階段,從首推到尾部: 假設當前的i是末尾,使得有三個鈾連在一起的情況分兩種:

1. 前i-1個已經滿足條件,此時數量是a[i-1]*2

2. 前i-2和i-1是鈾,第i-3個不是,此時數量是pow(2,i-4)-a[i-4];

對於第二種情況是有點噁心的,不過這麼想:i-4以前的再怎麼擺跟i也沒什麼卵關係,然後a[i-4]代表i-4做尾部,合法的擺法

#include<cstdio>
#include<cmath>
using namespace std;
long long a[50];
int main()
{
    int n;
    a[3]=1;a[4]=3;
    for(int i=4;i<=20;i++)
        a[i]=2*a[i-1]+(pow(2,i-4)-a[i-4]);
    while(scanf("%d",&n)!=-1)
        printf("%lld\n",a[n]);
    return 0;
}


全錯位排列

這個第一次見是裝信問題:求n封信全錯的裝填方案數

隨便挑出兩份A,B,設第i份為A且裝錯:

1. AB恰好對調,那麼這樣的B有i-1個

2. A錯了,但B不是A的信,那麼我們去掉A,設A的信裝在B裡是對的(因為B已經沒對應的信了),恰好劃分出一個子問題...(好巧~),這樣數量有a[i-2]*(i-1)份(這樣的B有i-1個)

最後加法原理...

#include<cstdio>
long long dp[20];
int main(){
    int n;
    dp[2]=1;dp[3]=2;
    for(int i=3;i<=20;i++)
        dp[i]=(dp[i-1]+dp[i-2])*(i-1);
    scanf("%d",&n);
    printf("%lld\n",dp[n]);
    return 0;
}

RPG難題

話說有n個格子,對這些格子塗色Red Pink Green ,要求相鄰顏色不同,首尾顏色不同,求塗色方案數....

筆者第一刷就考慮奇偶去了....(文科生!)

還是從首到尾列舉i為末尾的情況:

1. 若i-1和第一個同色: a[i]=2*a[i-2];(因為對於i-2來說,i-1可以算做第一個,然後i可以塗兩個色)

2. 若i-1和第一個異色:第i個只能塗一個顏色,所以a[i]=a[i-1]

最後分類加法....

#include<cstdio>
using namespace std;
long long dp[55];
int main(){
    int n;
    dp[1]=3;dp[2]=6;dp[3]=6;
    for(int i=4;i<=50;i++)dp[i]=2*dp[i-2]+dp[i-1];
    while(scanf("%d",&n)!=-1){
        printf("%lld\n",dp[n]);
    }
    return 0;
}

其實前面都水,是基礎,不過這個倒是有點意思...

和全錯位不同的是這裡要求n份信,放錯的沒超過一半的方案總數;(也就是對的在一半及以上...)

組合數學:乘法原理

列舉裝對的從一半到全部: 

設當前裝對K封,從n取k有C(n,k)種(文科生注意了,這是組合公式...),剩下的那些全錯的可能數有a[n-i]種(a是上面算出來的全錯位.....),再乘法原理

最後對列舉的那些用加法原理...(組合數學!文科生!)

#include<cstdio>
long long c[55],cnt;
int n;
long long C(int n,int m){
    double ans=1;
    for(int i=0;i<m;i++)
        ans*=(1.0*n-i)/(i+1.0);
    return (long long)ans;
}
int main(){
    int m,i;
    c[2]=1;
    for(int i=3;i<=25;i++)c[i]=(i-1)*(c[i-1]+c[i-2]);
    while(scanf("%d",&n)!=-1&&n){
        m=n/2+n%2;
        cnt=0;
        for(i=m;i<=n;i++)
            cnt+=C(n,i)*c[n-i];
        printf("%lld\n",cnt+1);
    }
    return 0;
}