Diablo III ZOJ - 3769

contiguous發表於2024-06-19

題目連結 https://vjudge.net/problem/ZOJ-3769

程式碼轉自:https://blog.csdn.net/Since_natural_ran/article/details/76037493?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171878667916800185844738%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171878667916800185844738&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-76037493-null-null.142v100control&utm_term=zoj3769

分組揹包的變式。

題意:

給出13類裝備,每種裝備都有攻擊力和防禦值,每種裝備只允許選擇一個,13種裝備裡如果選擇了雙手裝備,就不能選擇武器和護盾了,還有戒指可以雙手各帶一個,求滿足防禦值至少在M的情況下,攻擊力最大為多少

思路:

首先我們先雜湊把13類裝備對應到vector[]中

void init()
{
    mp["Weapon"] = 1;
    mp["Shield"] = 2;
    mp["Two-Handed"] = 3;
    mp["Finger"] = 4;
    mp["Feet"] = 5;
    mp["Legs"] = 6;
    mp["Waist"] = 7;
    mp["Wrist"] = 8;
    mp["Hand"] = 9;
    mp["Torso"] = 10;
    mp["Neck"] = 11;
    mp["Shoulder"] = 12;
    mp["Head"] = 13;
}

然後我們按照題意處理這些資料

 int length1 = G[1].size();
        int length2 = G[2].size();
        for(int j = 0;j < length2; j++) {
            G[3].push_back(G[2][j]);
        }
        for(int i = 0;i < length1; i++) {
            G[3].push_back(G[1][i]);
            for(int j = 0;j < length2; j++) {
                G[3].push_back((goods){G[1][i].Damage+G[2][j].Damage,G[1][i].Toughness+G[2][j].Toughness});
            }
        }
        int length3 = G[4].size();
        for(int i = 0;i < length3; i++) {
            for(int j = i + 1;j < length3; j++) {
                G[4].push_back((goods){G[4][i].Damage+G[4][j].Damage,G[4][i].Toughness+G[4][j].Toughness});
            }
        }

處理好了之後,就是一個分組揹包。

不過直接寫分組揹包像我這樣會TLE

設$dp[i][j]$表示前i個物品,韌度為j的最大攻擊力,然後我算了一個韌度和,是1500000

int dp[15][1500000];
int ans = 0;
   for (int i = 4; i <= 14; i++) {
      for (int j = 1500000; j >= 0; j--) {
         for (auto [a, b] : v[i]) {
            if(j >= a) {
               dp[i][j] = max(dp[i - 1][j - a] + b, dp[i][j]);
            }
         }
      }
   }
   for (int i = M; i <= 1500000; i++) {
      ans = max(ans, dp[14][i]);
   }
   
   if(ans == 0) {
      cout << "-1\n";
   } else {
      cout << ans << "\n";
   }

這樣寫就會mle+tle

我們來看看正解最佳化的多妙。

memset(dp,-1,sizeof(dp));
        dp[2][0] = 0;
        for(int i = 3;i <= 13; i++) {
            for(int j = m;j >= 0; j--) {
                dp[i][j] = max(dp[i][j],dp[i-1][j]);    //更新裝備狀態,即使後邊沒有裝備了,也能到到之前的狀態。(必須步驟)
                if(dp[i-1][j] == - 1) continue;     //若要買下一個裝備,那麼上一個裝備的狀態一定要達到
                int length = G[i].size();
                for(int k = 0;k < length; k++) {
                    goods e = G[i][k];
                    int d = min(e.Toughness + j,m); //如果大於m則當做m即可(極妙)
                    dp[i][d] = max(dp[i][d],dp[i-1][j]+e.Damage);
                }
            }
        }
        cout<<dp[13][m]<<endl;

我覺得有兩個地方很妙:

  • 他把第二維的上限設定成m,如果大於m等價於m,最佳化了空間,也最佳化了時間
  • 第二個是,這個題與平常的分組揹包不同的是,分組揹包是本金為m,能獲得的最優價值是多少。這個題是隻要用m去獲得價值,所以題解是從前向後dp。我覺得是這樣。

相關文章