1. 程式人生 > 其它 >P1541 烏龜棋

P1541 烏龜棋

技術標籤:記憶化搜尋動態規劃滾動陣列壓位陣列

題目背景

小明過生日的時候,爸爸送給他一副烏龜棋當作禮物。

題目描述

烏龜棋的棋盤是一行NN個格子,每個格子上一個分數(非負整數)。棋盤第1格是唯一的起點,第NN格是終點,遊戲要求玩家控制一個烏龜棋子從起點出發走到終點。

烏龜棋中MM張爬行卡片,分成4種不同的型別(MM張卡片中不一定包含所有44種類型的卡片,見樣例),每種型別的卡片上分別標有1,2,3,41,2,3,4四個數字之一,表示使用這種卡片後,烏龜棋子將向前爬行相應的格子數。遊戲中,玩家每次需要從所有的爬行卡片中選擇一張之前沒有使用過的爬行卡片,控制烏龜棋子前進相應的格子數,每張卡片只能使用一次。

遊戲中,烏龜棋子自動獲得起點格子的分數,並且在後續的爬行中每到達一個格子,就得到該格子相應的分數。玩家最終遊戲得分就是烏龜棋子從起點到終點過程中到過的所有格子的分數總和。

很明顯,用不同的爬行卡片使用順序會使得最終遊戲的得分不同,小明想要找到一種卡片使用順序使得最終遊戲得分最多。

現在,告訴你棋盤上每個格子的分數和所有的爬行卡片,你能告訴小明,他最多能得到多少分嗎?

輸入格式

每行中兩個數之間用一個空格隔開。

第11行22個正整數N,MN,M,分別表示棋盤格子數和爬行卡片數。

第22行NN個非負整數,a_1,a_2,…,a_Na1​,a2​,…,aN​,其中a_iai​表示棋盤第ii個格子上的分數。

第33行MM個整數,b_1,b_2,…,b_Mb1​,b2​,…,bM​,表示M張爬行卡片上的數字。

輸入資料保證到達終點時剛好用光MM張爬行卡片。

輸出格式

11個整數,表示小明最多能得到的分數。

輸入輸出樣例

輸入 #1複製

9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1

輸出 #1複製

73

說明/提示

每個測試點1s1s

小明使用爬行卡片順序為1,1,3,1,21,1,3,1,2,得到的分數為6+10+14+8+18+17=736+10+14+8+18+17=73。注意,由於起點是11,所以自動獲得第11格的分數66。

對於30\%30%的資料有1≤N≤30,1≤M≤121≤N≤30,1≤M≤12。

對於50\%50%的資料有1≤N≤120,1≤M≤501≤N≤120,1≤M≤50,且44種爬行卡片,每種卡片的張數不會超過2020。

對於100\%100%的資料有1≤N≤350,1≤M≤1201≤N≤350,1≤M≤120,且44種爬行卡片,每種卡片的張數不會超過4040;0≤a_i≤100,1≤i≤N,1≤b_i≤4,1≤i≤M0≤ai​≤100,1≤i≤N,1≤bi​≤4,1≤i≤M。

思路:

注意點:

1. 起點從第一個格子開始,所以獲得第一格的分數。

2. 到達終點正好用完卡片。

法一:動態規劃

狀態: dp[x1][x2][x3][x4]表示用x1張每次走1步的卡片,用x2張每次走2步的卡片,用x3張每次走3步的卡片,用x4張每次走4步的卡片最多能拿到的分數。

狀態轉移: dp[x1][x2][x3][x4]由下面四個狀態轉移而來

dp[x1-1][x2][x3][x4] (x1>0) (1)

dp[x1][x2-1][x3][x4] (x2>0) (2)

dp[x1][x2][x3-1][x4] (x3>0) (3)

dp[x1][x2][x3][x4-1] (x3>0) (4)

dp[x1][x2][x3][x4]=max(1,2,3,4)+a[pos], pos=x1+2*x2+3*x3+4*x4,即當前位置的值。

答案:dp[b[1]][b[2]][b[3]][b[4]];

邊界值:dp[0][0][0][0]=a[1];//起點就在第一格了。

#include <iostream>

using namespace std;

int b[5],a[500],dp[40][40][40][40];//dp[i][j][k][s]:表示1用i長,2用j張,.....最多走到的距離
int main()
{
    int n,m,x;
    cin>>n>>m;
    for(int i=1; i<=n; i++){
       cin>>a[i];
    }
    for(int i=1; i<=m; i++){
       cin>>x;
       b[x]++;
    }
    dp[0][0][0][0]=a[1];//注意剛開始站在第一格
    for(int i=0; i<=b[1]; i++){
       for(int j=0; j<=b[2]; j++){
          for(int k=0; k<=b[3]; k++){
            for(int s=0; s<=b[4]; s++){
                if(i==0&&j==0&&k==0&&s==0)
                     continue;
                int pos=i+j*2+3*k+s*4+1;
                if(i>0) dp[i][j][k][s]=max(dp[i][j][k][s],dp[i-1][j][k][s]+a[pos]);
                if(j>0) dp[i][j][k][s]=max(dp[i][j][k][s],dp[i][j-1][k][s]+a[pos]);
                if(k>0) dp[i][j][k][s]=max(dp[i][j][k][s],dp[i][j][k-1][s]+a[pos]);
                if(s>0) dp[i][j][k][s]=max(dp[i][j][k][s],dp[i][j][k][s-1]+a[pos]);
            }
          }
       }
    }
    cout<<dp[b[1]][b[2]][b[3]][b[4]]<<endl;
    return 0;
}

法二:思路跟法一 一樣,只不過用記憶化搜尋來寫。

#include <iostream>

using namespace std;

int b[5],a[500],dp[40][40][40][40],ans;//dp[i][j][k][s]:表示1用i長,2用j張,.....最多走到的距離
int dfs(int x1, int x2, int x3, int x4){
    if(dp[x1][x2][x3][x4]!=0)
        return dp[x1][x2][x3][x4];
    int pos=x1+2*x2+3*x3+4*x4+1;
    int ans=0;
    if(x1>0) ans=max(dfs(x1-1,x2,x3,x4)+a[pos],ans);
    if(x2>0) ans=max(dfs(x1,x2-1,x3,x4)+a[pos],ans);
    if(x3>0) ans=max(dfs(x1,x2,x3-1,x4)+a[pos],ans);
    if(x4>0) ans=max(dfs(x1,x2,x3,x4-1)+a[pos],ans);
    return dp[x1][x2][x3][x4]=ans;
}
/*
  記憶化搜尋框架
   if(已經計算過)//出口
   {
      return 計算過 值;
   }
   計算未計算的值
   1. 可能是取最值 。int ans=初始化;//
   for()//相鄰的點
   {
       遞迴計算不斷取最值;
   }
   2.或求和  int ans=初始化;//
   for()//相鄰的點
   {
       遞迴計算不斷求和;
   }
   ans賦值給當前所求。
   return dp[x][y]等。
*/
int main()
{
    int n,m,x;
    cin>>n>>m;
    for(int i=1; i<=n; i++){
       cin>>a[i];
    }
    for(int i=1; i<=m; i++){
       cin>>x;
       b[x]++;
    }
    dp[0][0][0][0]=a[1];
    cout<<dfs(b[1],b[2],b[3],b[4])<<endl;
    return 0;
}

法三:思路還是跟法一 一樣,但是可以用滾動陣列來寫。

可以理解為i為樓層,對於每一層樓,都有一個三維的集合 dp[0...b[2]][0...b[3]][0...b[4]];

對於每一個具體的三維點,都用到前一層樓的點來更新,從上到下,每一列的點都在不斷更新,使用同一層的空間。

#include <iostream>

using namespace std;

int b[5],a[500],dp[40][40][40];//dp[i][j][k][s]:表示1用i長,2用j張,.....最多走到的距離
int main()
{
    int n,m,x;
    cin>>n>>m;
    for(int i=1; i<=n; i++){
       cin>>a[i];
    }
    for(int i=1; i<=m; i++){
       cin>>x;
       b[x]++;
    }
    for(int i=0; i<=b[1]; i++){
       for(int j=0; j<=b[2]; j++){
          for(int k=0; k<=b[3]; k++){
            for(int s=0; s<=b[4]; s++){
                int pos=i+j*2+3*k+s*4+1;
                if(j>0) dp[j][k][s]=max(dp[j][k][s],dp[j-1][k][s]);
                if(k>0) dp[j][k][s]=max(dp[j][k][s],dp[j][k-1][s]);
                if(s>0) dp[j][k][s]=max(dp[j][k][s],dp[j][k][s-1]);
                dp[j][k][s]+=a[pos];
            }
          }
       }
    }
    cout<<dp[b[2]][b[3]][b[4]]<<endl;
    return 0;
}
//滾動陣列壓縮一維

法四:壓位陣列 +滾動陣列

類似於雜湊,通過某種計算方式將三維對映為一維。

因資料不超多41,所以採用41進位制。

#include <iostream>

using namespace std;

int b[5],a[500],dp[70650]; //41^3+41^2+41=70643大於這個數即可
int cal(int j,int k,int s){//看成41進位制,算出唯一對應的十進位制數,三維轉成一維陣列
    return j*1681+k*41+s;//j*41^2+k*41+s
}
int main()
{
    int n,m,x;
    cin>>n>>m;
    for(int i=1; i<=n; i++){
       cin>>a[i];
    }
    for(int i=1; i<=m; i++){
       cin>>x;
       b[x]++;
    }
    for(int i=0; i<=b[1]; i++){
       for(int j=0; j<=b[2]; j++){
          for(int k=0; k<=b[3]; k++){
            for(int s=0; s<=b[4]; s++){
                int pos=i+j*2+3*k+s*4+1;
                if(j>0) dp[cal(j,k,s)]=max(dp[cal(j,k,s)],dp[cal(j-1,k,s)]);
                if(k>0) dp[cal(j,k,s)]=max(dp[cal(j,k,s)],dp[cal(j,k-1,s)]);
                if(s>0) dp[cal(j,k,s)]=max(dp[cal(j,k,s)],dp[cal(j,k,s-1)]);
                dp[cal(j,k,s)]+=a[pos];
            }
          }
       }
    }
    cout<<dp[cal(b[2],b[3],b[4])]<<endl;
    return 0;
}