記憶化搜尋的學習經歷——洛谷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;
}