Java算法學習:藍橋杯——地宮尋寶(DFS+動態規劃—記憶型遞歸)

Java算法學習:藍橋杯——地宮尋寶(DFS✖記憶型遞歸)

題目:

標題:地宮取寶

    X 國王有一個地宮寶庫。是 n x m 個格子的矩陣。每個格子放一件寶貝。每個寶貝貼着價值標籤。

    地宮的入口在左上角,出口在右下角。

    小明被帶到地宮的入口,國王要求他只能向右或向下行走。

    走過某個格子時,如果那個格子中的寶貝價值比小明手中任意寶貝價值都大,小明就可以拿起它(當然,也可以不拿)。

    當小明走到出口時,如果他手中的寶貝恰好是k件,則這些寶貝就可以送給小明。

    請你幫小明算一算,在給定的局面下,他有多少種不同的行動方案能獲得這k件寶貝。

【數據格式】

    輸入一行3個整數,用空格分開:n m k (1<=n,m<=50, 1<=k<=12)

    接下來有 n 行數據,每行有 m 個整數 Ci (0<=Ci<=12)代表這個格子上的寶物的價值

    要求輸出一個整數,表示正好取k個寶貝的行動方案數。該數字可能很大,輸出它對 1000000007 取模的結果。

例如,輸入:
2 2 2
1 2
2 1
程序應該輸出:
2

再例如,輸入:
2 3 2
1 2 3
2 1 5
程序應該輸出:
14

資源約定:
峯值內存消耗(含虛擬機) < 256M
CPU消耗  < 2000ms


請嚴格按要求輸出,不要畫蛇添足地打印類似:“請您輸入...” 的多餘內容。

所有代碼放在同一個源文件中,調試通過後,拷貝提交該源碼。
注意:不要使用package語句。不要使用jdk1.7及以上版本的特性。
注意:主類的名字必須是:Main,否則按無效代碼處理。

思路:

首先看到這個矩陣型+只能向右/向下的搜索,首先想到類似“2013年振興中華”這一題的dfs解法,那麼就按照這一個思路去解決這個問題。

首先要有一個主函數,和一個dfs函數,dfs函數的參數很重要,分別設定爲。x座標,y座標,max當前最大值,和cnt作爲計算寶物數量的計數器變量,用來判斷是否拿滿了k件寶物。

當小明走到某一步的時候,如果當前取出的寶物比k大,就不在搜索這條分支,return 0;

dfs裏面要注意的是,一定要有:

  1. 當前格子的價值比max的值大然後向右走/向下走並且拿起寶物
  2. 當前格子的價值比max的值 大或者小但是向右走/向下走但是不拿起寶物

這樣四種情況的遞歸調用。

當走到最右下角的時候,這時候有兩種情況,一個是取滿了k件寶物,可以return了,另外一種是還差一件取滿k件寶物,並且最後一格上面放着的寶物比當前已有的max還大,就再取出一件寶物來。

代碼實現具體:


    private static final int MOD = 1000000007;
    private static int n;
    private static int m;
    private static int k;
    private static long ans = 0;
    //存儲每次遞歸的結果
    private static long[][][][] cache = new long[51][51][14][14];

    private static int[][] table = {
            {1,2,3},{2,1,5}
    };

    public static void main(String[] args) {
        //行
        n=2;
        //列
        m=3;
        //需要的寶物數量
        k=2;
        //緩存數組初始化爲-1
        ans = dfs(0,0,-1,0);
        System.out.println(ans);
    }

    /**
     * @param x
     * @param y
     * @param max
     * @param cnt
     * @return
     */
    private static long dfs(int x,int y,int max,int cnt){
        //溢出防禦,遞歸出口
        if(x==n||y==m||cnt>k){
            return 0;
        }

        int value = table[x][y];
        long ans = 0;
        //走到最後一個格子前,遞歸出口
        if(x==n-1&&y==m-1){
            //一種情況是已經取滿了k件寶物 另外一種情況是還差一件,但是最後一件的價值正好大於max
            if(cnt==k||(cnt==k-1&&value>max)){
                return 1;
            }
            return ans;
        }

        //如果當前格子的價值比最大值大,取出的情況
        if(value>max){
            //取出這個物品
            ans+=dfs(x,y+1,value,cnt+1);
            ans+=dfs(x+1,y,value,cnt+1);
        }

        //價值小或者價值大,但是不取出的情況
        ans+=dfs(x,y+1,max,cnt);
        ans+=dfs(x+1,y,max,cnt); 
        return ans%MOD;
    }

上述代碼都依照dfs深度搜索的基本題型進行書寫即可,難點就在一跳出條件很複雜,遞歸的情況分支也很複雜。

並且這樣做,遞歸的效率很低。當數據量比較大的時候,run程序的時間就比較長,這樣給分就不高。

改進:

針對上面說的這種問題,我們觀察題目,由於這個問題中,DFS函數的參數是有可能相同的。

比如中間的任意一個格子,可以是來自多個方向,並且從每個方向過來的小明,身上攜帶的寶物最大價值和寶物數量都有可能一樣。

上面這種情況就是所謂的存在着重複子問題。這些具有相同參數的dfs函數在之前的代碼裏就需要反覆的去計算,這對計算機的內存和運行時間都是極大的浪費。因此採用動態規劃記憶型遞歸的方法來的方式來解決。

所謂記憶型遞歸就是吧相同參數的dfs函數的結果用一個長度爲(i*j*n*m)的數組保存,ijnm分別是dfs參數可能取到的範圍,比如題目中就有提示,這個記憶數組的長度就是51*51*14*14。當我們在這個數組中保存結果的時候,每一次調用dfs都從中查找相應的單元是否存在着之前記錄下來的答案。如果有,就返回,不用多次的去調用求解。這樣就節省了cpu資源和時間。

改進後的代碼實現:

public class _地宮取寶記憶型遞歸 {

    private static final int MOD = 1000000007;
    private static int n;
    private static int m;
    private static int k;
    private static long ans = 0;
    //存儲每次遞歸的結果
    private static long[][][][] cache = new long[51][51][14][14];

    private static int[][] table = {
            {1,2,3},{2,1,5}
    };

    public static void main(String[] args) {
        //行
        n=2;
        //列
        m=3;
        //需要的寶物數量
        k=2;
        //緩存數組初始化爲-1
        for (int i = 0; i <51 ; i++) {
            for (int j = 0; j < 51; j++) {
                for (int l = 0; l < 14; l++) {
                    for (int o = 0; o < 14; o++) {
                        cache[i][j][l][o] = -1;
                    }
                }
            }
        }
        ans = dfs(0,0,-1,0);
        System.out.println(ans);
    }

    /**
     * @param x
     * @param y
     * @param max
     * @param cnt
     * @return
     */
    private static long dfs(int x,int y,int max,int cnt){
        //查詢緩存是否有記錄,若有,返回記錄
        if(cache[x][y][max+1][cnt]!=-1){
            return cache[x][y][max+1][cnt];
        }

        //溢出防禦,遞歸出口
        if(x==n||y==m||cnt>k){
            return 0;
        }

        int value = table[x][y];
        long ans = 0;
        //走到最後一個格子前,遞歸出口
        if(x==n-1&&y==m-1){
            //一種情況是已經取滿了k件寶物 另外一種情況是還差一件,但是最後一件的價值正好大於max
            if(cnt==k||(cnt==k-1&&value>max)){
                return 1;
            }
            return ans;
        }

        //如果當前格子的價值比最大值大,取出的情況
        if(value>max){
            //取出這個物品
            ans+=dfs(x,y+1,value,cnt+1);
            ans+=dfs(x+1,y,value,cnt+1);
        }

        //價值小或者價值大,但是不取出的情況
        ans+=dfs(x,y+1,max,cnt);
        ans+=dfs(x+1,y,max,cnt);

        //寫緩存

        cache[x][y][value][cnt] = ans%MOD;
        return ans%MOD;
    }
}

 

總結:第一次接觸到DP,以後會好好學習和理解動態規劃這個知識點。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章