藍橋杯 地宮取寶 記憶深度搜索+兩種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次,求組合。也就是
問題變爲組合數取餘,m,n數量不大,使用楊輝三角的公式就可以求解
賦初值後,再進行以下dp
如果不取,則能更新以下狀態
如果取,還要更新以下狀態
#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還能優化,因爲看到了求和 和 狀態大部分只使用一次,但是因爲到了四維,我已經想象不出來了。。。但是總有大佬能超越維度。。。。暫且先這樣吧