1. 程式人生 > >記憶化搜尋的學習經歷——洛谷P1464:Function詳解

記憶化搜尋的學習經歷——洛谷P1464:Function詳解

前兩天,我在刷洛谷題的時候,遇見了這樣一道題,題目是這樣的:

題目描述

對於一個遞迴函式w(a,b,c)

如果a<=0 or b<=0 or c<=0就返回值1.

如果a>20 or b>20 or c>20就返回w(20,20,20)

如果a<b並且b<c 就返回w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c)

其它別的情況就返回w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1)

這是個簡單的遞迴函式,但實現起來可能會有些問題。當a,b,c均為15時,呼叫的次數將非常的多。你要想個辦法才行.

/*

absi2011 : 比如 w(30,-1,0)既滿足條件1又滿足條件2

這種時候我們就按最上面的條件來算

所以答案為1

*/

輸入輸出格式

輸入格式:

會有若干行.

並以-1,-1,-1結束.

保證輸入的數在-9223372036854775808~9223372036854775807之間

並且是整數

輸出格式:

輸出若干行

格式:

[b]w(a,_b,c)=_你的輸出(_代表空格)[/b]

輸入輸出樣例

輸入樣例#1:
1 1 1
2 2 2
-1 -1 -1
輸出樣例#1:
w(1, 1, 1) = 2
w(2, 2, 2) = 4

說明

記憶化搜尋

-----------------------------------------------------------------------------分割線-----------------------------------------------------------------------------

好,讓我開始吧。

先看題目,看起來很簡單誒,直接遞迴呼叫不就得了?

正當我開始寫時,卻發現下面有兩行字:

說明

記憶化搜尋


等等,記憶化搜尋是個什麼玩意啊?管他呢,先寫完提交上去再說。於是我開開心心地寫完了。

提交後,卻發現TLE,這是為啥啊?於是我拿起筆算了一下,這個時間複雜度竟然高達a^n,怪不得超時。這時我才開始仔細研究“記憶化搜尋”這五個字。

先看看“搜尋”,這個肯定所有人都懂啊,就是深度優先搜尋的那個搜尋啊。那什麼叫“記憶化”呢?我想啊想,想到了剛剛提交的超時程式,才發現有很多重複計算的,比如在計算w(1,2,3)和w(2,2,3)時,都需要用到w(1,1,2),如果輸入時數滿足既需要w(1,2,3),也需要w(2,2,3),那就無異於算了兩遍w(1,1,2)

那麼解決方法瞬間就浮現在我眼前了:既然可能會用兩遍w(1,1,2)。那在第一遍肯定需要先算一遍,第二次要用的時候,直接用第一次的結果,不就可以了麼?

那麼問題又來了,第二次要怎麼用呢?我想到了一個非常簡單的方法:直接用一個數組把算出來的結果儲存下來,下次要用的時候先判斷是否算過w(1,1,2),如果算過,就直接用好了。這難道不就是所謂的“記憶化”嗎?!

有了這個思路,程式碼就很簡單了,我寫道:

int w(int a,int b,int c){
    if(a<=0||b<=0||c<=0) return 1;

//不解釋
    if(s[a][b][c]) return s[a][b][c];

//用s[a][b][c]來儲存,如果已經計算過一次,就不用再進行計算了
    else if(a>20||b>20||c>20) s[a][b][c]=w(20,20,20);

//不解釋
    else if(a<b&&b<c) s[a][b][c]=w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c);

//把計算結果儲存在s陣列中
    else s[a][b][c]=w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);

//同上
    return s[a][b][c];

//不解釋

}

再提交上去,完美AC,原來傳說中的記憶化搜尋時這個意思啊!

-----------------------------------------------------------------------------分割線-----------------------------------------------------------------------------

最後附上完整程式碼:

#include <cstdio>

using namespace std;

int s[21][21][21];

long long int a[1000],b[1000],c[1000];

int a1[1000],b1[1000],c1[1000];

int w(int a,int b,int c){

    if(a<=0||b<=0||c<=0) return 1;

    if(s[a][b][c]) return s[a][b][c];

    else if(a>20||b>20||c>20) s[a][b][c]=w(20,20,20);

    else if(a<b&&b<c) s[a][b][c]=w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c);  

    else s[a][b][c]=w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);

    return s[a][b][c];

}

int main(){

    int result[100];

    int i=0;

    int j=0;

    while(1){

        scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);

        if(a[i]==-1&&b[i]==-1&&c[i]==-1) break;

        if(a[i]>21||b[i]>21||c[i]>21) a1[i]=b1[i]=c1[i]=21;

        else{

            a1[i]=a[i];

            b1[i]=b[i];

            c1[i]=c[i];

        }

        result[i]=w(a1[i],b1[i],c1[i]);

        i++;

    }

    for(j=0;j<i;j++){

        printf("w(%lld, %lld, %lld) = %d\n",a[j],b[j],c[j],result[j]);

    }

    return 0;

}