藍橋杯 地宮取寶 記憶深度搜索+兩種dp解法

藍橋杯 地宮取寶 記憶深度搜索+兩種dp解法

資源限制
時間限制:1.0s 內存限制:256.0MB
問題描述
  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

看到這個題目就想到dp,但是寫了一下轉移方程發現寫不出,看了題解用了記憶化搜索,原理就是記憶已經計算過的dfs,也是很常用的一種方法。一開始覺得深搜有點難寫,看規模還拿不了太多分,所以就沒往深搜想,後來聽說暴力深搜也能拿60分。

1. 記憶化搜索

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 1000000007
int n,m,k;
int mm[55][55];
LL cache[55][55][15][15];

LL sum=0;
LL dfs(int x,int y,int maxs,int cnt){//點到終點的方案數
    if(cache[x][y][maxs][cnt]!=-1)
        return cache[x][y][maxs][cnt];

    if(x==n||y==m||cnt>k)
        return cache[x][y][maxs][cnt]=0;
    LL s=0;

    if(x==n-1&&y==m-1)
        if(cnt==k||(cnt==k-1&&mm[x][y]>maxs))
            return cache[x][y][maxs][cnt]=1;


    if(mm[x][y]>maxs){
        s+=dfs(x+1,y,mm[x][y],cnt+1);
        s%=MOD;
        s+=dfs(x,y+1,mm[x][y],cnt+1);
        s%=MOD;
    }
    s+=dfs(x+1,y,maxs,cnt);
    s%=MOD;
    s+=dfs(x,y+1,maxs,cnt);
    s%=MOD;
    return cache[x][y][maxs][cnt]=s;
}

int main(){
    cin>>n>>m>>k;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++){
            cin>>mm[i][j];
            mm[i][j]++;
         }
    memset(cache,-1,sizeof (cache));

    printf("%lld\n",dfs(0,0,0,0));


}

此處有個小變化,將輸入數據整體+1,使其範圍變爲從1開始,這樣可以定義無拿取的點的最大價值爲0.

2.dp(略走彎路)

寫完了記憶化搜索,感覺dp還是可以,於是又想了想。
寫出dp公式

對於一個已知的點dp[i][j][k][l]要轉移只有兩個方向,每個方向只有兩種可能,拿或者不拿。

此時我們認爲不取的狀態數爲0,也就是k==0時,dp恆爲0 注意此處,我在此處走了彎路。如果那麼思考問題將會複雜很多。

那麼dp[i][j][1][m[i][j]]的初值需要我們賦值,觀察到對於每一條終點爲i,j的路徑,都對應着一種方案,該種方案k=1,l=m[i][j]即爲取該點本身。也就是初值應當賦值爲終點爲i,j的路徑數量。問題轉化爲求終點爲i,j的路徑數量,考慮需要橫向操作j-1次,縱向操作i-1次,求組合。也就是

(n+m2)!(n1)!(m1)!=C(m+n2,n1)=C(m+n2,m1) \frac{(n+m-2)!}{(n-1)!(m-1)!}=C(m+n-2,n-1)=C(m+n-2,m-1)

問題變爲組合數取餘,m,n數量不大,使用楊輝三角的公式就可以求解
C(n,m)=C(n1,m)+C(n1,m1) C(n,m)=C(n-1,m)+C(n-1,m-1)
賦初值後,再進行以下dp

如果不取,則能更新以下狀態
dp[i+1][j][k][l]+=dp[i][j][k][l]dp[i+1][j][k][l]+=dp[i][j][k][l]
dp[i][j+1][k][l]+=dp[i][j][k][l]dp[i][j+1][k][l]+=dp[i][j][k][l]

如果取,還要更新以下狀態
dp[i+1][j][k][mm[i+1][j]]+=dp[i][j][k1][l];dp[i+1][j][k][mm[i+1][j]]+=dp[i][j][k-1][l];
dp[i][j+1][k][mm[i][j+1]]+=dp[i][j][k1][l];dp[i][j+1][k][mm[i][j+1]]+=dp[i][j][k-1][l];

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 1000000007
int n,m,kk;
int mm[55][55];
LL dp[55][55][15][15];//i,j 處 取k個 最大值l
LL C[105][105];

void init(){
    C[0][0]=1;
    for(int i=1;i<=100;i++){
        C[i][0]=C[i][i]=1;
        for(int j=1;j<=i/2+1;j++)
            C[i][j]=C[i][i-j]=(C[i-1][j-1]+C[i-1][j])%MOD;

    }
}
int main(){
    init();
    cin>>n>>m>>kk;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            cin>>mm[i][j];
            mm[i][j]++;
            dp[i][j][1][mm[i][j]]=C[i+j-2][i-1];
           
         }

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=1;k<=12;k++)
                for(int l=0;l<=13;l++){
                    dp[i+1][j][k][l]+=dp[i][j][k][l];dp[i+1][j][k][l]%=MOD;
                    dp[i][j+1][k][l]+=dp[i][j][k][l];dp[i][j+1][k][l]%=MOD;
                    if(mm[i+1][j]>l)
                        dp[i+1][j][k][mm[i+1][j]]+=dp[i][j][k-1][l];dp[i+1][j][k][mm[i+1][j]]%=MOD;
                    if(mm[i][j+1]>l)
                        dp[i][j+1][k][mm[i][j+1]]+=dp[i][j][k-1][l];dp[i][j+1][k][mm[i][j+1]]%=MOD;
                }
        
        LL ans=0;
        for(int i=0;i<=13;i++)
            ans+=dp[n][m][kk][i],ans%=MOD;
    printf("%lld\n",ans);


}

3.dp(正解 我猜的 )

方程和上面無差,主要在取個數爲0的時候,考慮這種情況不是0,也就是說認爲一個都不取也是一種方案

爲什麼要那麼考慮?上面做法k從1開始考慮,似乎沒有問題,但是對於k=1時的初始值運用了組合數進行運算,組合數還略顯麻煩。不如考慮將動態規劃延伸到k=0進行統一。

現在難以解決的是k=1的情況,k=1的情況是從哪來的呢,一種思路就是上面組合數求,另一種,認爲k=1的情況是根據轉移方程來的,也就是k=1來自上一個位置的k=0的方案數,那k=0的方案數又是怎麼來的,k=0相當於,上兩個位置k=0之後,這一次也沒有拿的情況,也就是將上兩個位置上的k=0的方案數相加,其實際意義就和組合數意義一樣,啥也不選,值爲起點到該點的路徑數量。

由此避開了複雜的組合數運算,此題中組合數運算還不算麻煩,只是普通楊輝三角,數量大了就要用盧卡斯定理,但是不管怎麼說,各種意義上都是下面的方法來的優秀。

通過對幾行代碼的修改,就可以完全避免篇幅巨大的組合數取模運算。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 1000000007
int n,m,kk;
int mm[55][55];
LL dp[55][55][15][15];//i,j 處 取k個 最大值l的方案數

int main(){
    cin>>n>>m>>kk;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            cin>>mm[i][j];
            mm[i][j]++;
         }


    dp[1][1][0][0]=dp[1][1][1][mm[1][1]]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<=12;k++)
                for(int l=0;l<=13;l++){
                    dp[i+1][j][k][l]+=dp[i][j][k][l];dp[i+1][j][k][l]%=MOD;
                    dp[i][j+1][k][l]+=dp[i][j][k][l];dp[i][j+1][k][l]%=MOD;
                    if(mm[i+1][j]>l)
                        dp[i+1][j][k][mm[i+1][j]]+=dp[i][j][k-1][l];dp[i+1][j][k][mm[i+1][j]]%=MOD;
                    if(mm[i][j+1]>l)
                        dp[i][j+1][k][mm[i][j+1]]+=dp[i][j][k-1][l];dp[i][j+1][k][mm[i][j+1]]%=MOD;
                }

        LL ans=0;
        for(int i=0;i<=13;i++)
            ans+=dp[n][m][kk][i],ans%=MOD;
    printf("%lld\n",ans);


}

當然,可能dp還能優化,因爲看到了求和 和 狀態大部分只使用一次,但是因爲到了四維,我已經想象不出來了。。。但是總有大佬能超越維度。。。。暫且先這樣吧

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