1. 程式人生 > 遊戲 >本週Fami通評分出爐 《人間地獄》獲31分評價

本週Fami通評分出爐 《人間地獄》獲31分評價

什麼是記憶化搜尋?

搜尋的低效在於沒有能夠很好地處理重疊子問題;動態規劃雖然比較好地處理了重疊子問題,但是在有些拓撲關係比較複雜的題目面前,又顯得無奈。記憶化搜尋正是在這樣的情況下產生的,它採用搜尋的形式和動態規劃中遞推的思想將這兩種方法有機地綜合在一起,揚長避短,簡單實用,在資訊學中有著重要的作用。

用一個公式簡單地說:記憶化搜尋=搜尋的形式+動態規劃的思想。

動態規劃:就是一個最優化問題,先將問題分解為子問題,並且對於這些分解的子問題自身就是最優的才能在這個基礎上得出我們要解決的問題的最優方案,要不然的話就能找到一個更優的解來替代這個解,得出新的最優自問題,這當然是和前提是矛盾的。動態規劃不同於 貪心演算法,因為貪心演算法是從區域性最優來解決問題,而動態規劃是全域性最優的。用動態規劃的時候不可能在子問題還沒有得到最優解的情況下就做出決策,而是必須等待子問題得到了最優解之後才對當下的情況做出決策,所以往往動態規劃都可以用 一個或多個遞迴式來描述。而貪心演算法卻是先做出一個決策,然後在去解決子問題。這就是貪心和動態規劃的不同。

一般遇到一個動態規劃型別的問題,都先要確定最優子結構,還有重疊子問題,這兩個是動態規劃最大的特徵,然後就是要寫 動態規劃的狀態方程,這個步驟十分十分的重要的,寫動歸方程是需要一定的經驗的,這可以通過訓練來達到目的。接著就是要自底向上的求解問題的,先將最小規模的子問題的最優解求出,一般都用一張表來記錄下求得的解,到後來遇到同樣的子問題的時候就可以直接查表得到答案,最後就是通過一步一步的迭代得出最後問題的答案了。

我的理解最重要的東西就是一定會要一個數組或者其他的儲存結構儲存得到的子問題的解。這樣就可以省很多時間,也就是典型的空間換時間。

動態規劃的一種變形就是記憶化搜尋,就是根據動歸方程寫出遞迴式,然後在函式的開頭直接返回以前計算過的結果,當然這樣做也需要一個儲存結構記下前面計算過的結果,所以又稱為記憶化搜尋。

記憶化搜尋遞迴式動態規劃

1.記憶化搜尋的思想

記憶化搜尋的思想是,在搜尋過程中,會有很多重複計算,如果我們能記錄一些狀態的答案,就可以減少重複搜尋量

2、記憶化搜尋的適用範圍

根據記憶化搜尋的思想,它是解決重複計算,而不是重複生成,也就是說,這些搜尋必須是在搜尋擴充套件路徑的過程中分步計算的題目,也就是“搜尋答案與路徑相關”的題目,而不能是搜尋一個路徑之後才能進行計算的題目,必須要分步計算,並且搜尋過程中,一個搜尋結果必須可以建立在同類型問題的結果上,也就是類似於動態規劃解決的那種。

也就是說,他的問題表達,不是單純生成一個走步方案,而是生成一個走步方案的代價等,而且每走一步,在搜尋樹/圖中生成一個新狀態,都可以精確計算出到此為止的費用,也就是,可以分步計算,這樣才可以套用已經得到的答案。

3、記憶化搜尋的核心實現

  • a. 首先,要通過一個表記錄已經儲存下的搜尋結果,一般用雜湊表實現

  • b.狀態表示,由於是要用雜湊表實現,所以狀態最好可以用數字表示,常用的方法是把一個狀態連寫成一個p進位制數字,然後把這個數字對應的十進位制數字作為狀態

  • c.在每一狀態搜尋的開始,高效的使用雜湊表搜尋這個狀態是否出現過,如果已經做過,直接呼叫答案,回溯

  • d.如果沒有,則按正常方法搜尋

4、記憶化搜尋是類似於動態規劃的,不同的是,它是倒做的“遞迴式動態規劃”。

模板

下面就這道題來講記憶化搜尋基本模板,用dp[i][j]表示從(i,j)出發吃到的cheese最多的值,用(x,y)表示其鄰點,則:

dp[i][j]=max(dp[i][j],a[i][j]+dp[x][y])(滿足a[x][y]>a[i][j]時)。在這個遞推式中,如果dp[i][j]被計算過一次,其值就是最終值,不用再計算。但是如果使用一般的dfs,則會出現大量重複計算一個值的情況,必然會超時。所以採用記憶化搜尋,在遞推過程中如果一個值被計算過(>=0),直接返回即可,否則計算其最終值並賦給它。也可以使用DP做這道題,但是要先排序,按照順序來DP,但比較麻煩。而記憶化搜尋則簡潔自然許多,其實質當然就是DP。

程式碼實現:


#include<bits/stdc++.h>
using namespace std;

int n,k;
int a[105][105],dp[105][105];
int go[4][2]={-1,0,0,1,1,0,0,-1};

int dfs(int x,int y){
    if(dp[x][y]>=0) return dp[x][y];
    dp[x][y]=a[x][y];
    for(int i=0;i<4;++i)
        for(int j=1;j<=k;++j){
            int xx=x+j*go[i][0],yy=y+j*go[i][1];
            if(xx>=0&&xx<n&&yy>=0&&yy<n&&a[xx][yy]>a[x][y])
                dp[x][y]=max(dfs(xx,yy)+a[x][y],dp[x][y]);
        }
    return dp[x][y];
}

int main(){
   while(~scanf("%d%d",&n,&k)&&n!=-1){//while(~scanf("%d%d", &n,&k))就是當沒有輸入的時候退出迴圈
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                scanf("%d",&a[i][j]);
        memset(dp,-1,sizeof(dp));
        printf("%d\n",dfs(0,0));
    }
    return 0;
}