1. 程式人生 > 實用技巧 >演算法筆記之DP實戰(二)

演算法筆記之DP實戰(二)

最大最小值DP

Choose minimum (maximum) path among all possible paths before the current state, then add value for the current state.
routes[i] = min(routes[i-1], routes[i-2], ... , routes[i-k]) + cost[i] 有時候也會min(f[x-1]+1, f[x]), 特別時重複走過x

746. Min Cost Climbing Stairs

到i的路徑來自於i-1和i-2。最後一步可以為n-1或者n的總cost。

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int ss = cost.size();
        vector <int> memo (ss+1, 0x6fffffff);
        memo[0] = 0; memo[1] = cost[0];
        for (int i=2; i<=ss; i++) {
            memo[i] = min(memo[i-1], memo[i-2]) + cost[i-1]; 
        }        
        return min(memo[ss-1], memo[ss]);
    }
};

64. Minimum Path Sum

Note: You can only move either down or right at any point in time.
f[n][m] = min(f[n-1][m], f[n][m-1]) + f[n][m];

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<int>> memo(n+1, vector<int>(m+1));
        // 給memo[0][1], memo[1][0]賦初值0
        for (int i=2; i<=n; i++) {
            memo[i][0] = 0x6fffffff;
        }
        for (int i=2; i<=m; i++) {
            memo[0][i] = 0x6fffffff;
        }
        
        for (int i=1; i<=n; i++) {
            for (int j=0; j<=m; j++) {
              
                memo[i][j] = min(memo[i-1][j], memo[i][j-1]) + grid[i-1][j-1];
            }
        }
        return memo[n][m];
    }
};

改良版->不需要memo, 用if處理padding,

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        // f[n][m] = min(f[n-1][m], f[n][m-1]) + f[n][m];
        
        for (int i=0; i<n; i++) {
            for (int j=0; j<m; j++) {
                if (j>0 && i>0) 
                    grid[i][j] += min(grid[i-1][j], grid[i][j-1]);
                else if (i>0)
                    grid[i][j] += grid[i-1][j];
                else if (j>0)
                    grid[i][j] += grid[i][j-1];
            }
        }

        return grid[n-1][m-1];
    }
};

322. Coin Change

f[n] = min(f[n-coins[i]], f[n-coins[i+1]], ....) + 1; memo[0] = 0; 如果memo[amount]是0x6fffffff,則表示沒走到這過(沒發生過更新)返回-1。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int ss = coins.size();
        vector<int> memo(amount+1, 0x6fffffff);
        memo[0] = 0;
        for (int i=1; i<=amount; i++) {
            for (int j=0; j<ss;  j++) {
                if (coins[j]>i) continue;
                memo[i] = min(memo[i-coins[j]] + 1, memo[i]);
            }
        }
        return memo[amount] != 0x6fffffff? memo[amount] : -1;
    }
};

931. Minimum Falling Path Sum

類似 Minimum Path Sum,
f[x][y] = min(f[x-1][y], f[x-1][y-1], f[x-1][y+1]) + f[x][y], 可直接在矩陣上操作, 然後判斷邊緣條件決定轉移方程(e.g. y==0時候,忽略f[x-1][y-1])。

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& A) {
        
        int n = A.size(), m = A[0].size();
        for (int x=1; x<n; x++) {
            for (int y=0; y<m; y++) {
                if (y && m-1-y) 
                    A[x][y] += min(A[x-1][y-1], min(A[x-1][y], A[x-1][y+1]));
                else if (y)
                    A[x][y] += min(A[x-1][y], A[x-1][y-1]);
                else if (m-1-y)
                    A[x][y] += min(A[x-1][y], A[x-1][y+1]);
                else 
                    A[x][y] += A[x-1][y];
            }
        }

        return *min_element(A[n-1].begin(), A[n-1].end());
    }
};

!983. Minimum Cost For Tickets Medium

f[day]相當於當日若出行,最低總票價
f[day] = min(f[day-1]+ticket_1, f[day-7]+ticket_2, f[day-30]+ticket_2); if f[day] not in days -> f[day] = f[day-1];

class Solution {
public:
    int mincostTickets(vector<int>& days, vector<int>& costs) {

        int max_day = days[days.size()-1];
        vector <int> memo(max_day+1);
        
        int idx = 0; // memo[0] = *min_element(costs.begin(), costs.end());
        for (int i=1; i<=max_day; i++) {
            if (i<days[idx]) memo[i] = memo[i-1];
            else {
                if (i>29)
                    memo[i] = min(min(memo[i-1]+costs[0], 
                                      memo[i-7]+costs[1]), 
                                      memo[i-30]+costs[2]);
                else if (i>6) 
                    memo[i] = min(min(memo[i-1]+costs[0], 
                                      memo[i-7]+costs[1]),
                                      costs[2]);
                else 
                    memo[i] = min(min(memo[i-1]+costs[0], 
                                      costs[1]),
                                      costs[2]);
                
                idx ++;
            }
        }
        
        return memo[max_day];
    }
};

!650. 2 Keys Keyboard Medium

更新都是來自公約數,所以對2n和1n/2迴圈
dp[n] = min(dp[n], dp[n/cnt] + n/cnt + 1 - 1); AA 複製到 AAAA只要一次貼上操作

class Solution {
public:
    int minSteps(int n) {
        if (n == 1) return 0;
        vector<int> dp(n+1, 0x6fffffff);
        dp[1] = 0, dp[2] = 2;   
        for (int i=3; i<=n; i++) {
            for (int j=i/2; j>0; j--) {
                if (i%j) continue;
                // AA -> AA AA just need to copy once!
                dp[i] = min(dp[i], dp[j] + i/j + 1 - 1);
            }
        }
        return dp[n];
    }
};

再簡化版 -> f[2]也符合規律

class Solution {
public:
    int minSteps(int n) {
        vector<int> dp(n+1, 0x6fffffff);
        dp[1] = 0;   
        for (int i=2; i<=n; i++) {
            for (int j=i/2; j>0; j--) {
                if (i%j) continue;
                // AA -> AA AA just need to paste once! and copy also needs one operation. so 1-1;
                dp[i] = min(dp[i], dp[j] + i/j + 1 - 1);
            }
        }
        return dp[n];
    }
};

279. Perfect Squares Medium

f[n] = min(f[n-sqr_num[i]] + 1, f[n]);

int numSquares(int n) {
        
        if (!n) return 0;
        vector<int> dp(n+1, 0x6fffffff), sqr_nums;
        for (int i=1; i<=static_cast<int>(sqrt(n)); i++) {
            sqr_nums.push_back(pow(i, 2));
        }
        int ss = sqr_nums.size();
        dp[0]=0;
        
        for (int i = 1; i<=n; i++) {
            for (int j = 0; j < ss; j++) {
                if (sqr_nums[j]>i) break;
                dp[i] = min(dp[i-sqr_nums[j]] + 1, dp[i]);
            }
        }
        return dp[n];
    }
};

簡單優化
找小於等於n的次方for (int i=1; ii<=n; i++) sqr_nums.push_back(ii);

class Solution {
public:
    int numSquares(int n) {
        // f[n] = min(f[n-sqr_num[i]] + 1, f[n]);

        vector<int> dp(n+1, 0x6fffffff), sqr_nums;
        for (int i=1; i*i<=n; i++) sqr_nums.push_back(i*i);

        int ss = sqr_nums.size();
        dp[0]=0;
        
        for (int i = 1; i<=n; i++) {
            for (int j = 0; j < ss; j++) {
                if (sqr_nums[j]>i) break;
                dp[i] = min(dp[i-sqr_nums[j]] + 1, dp[i]);
            }
        }
        return dp[n];
    }
};

120. Triangle Medium

類似931. Minimum Falling Path Sum

dp[i][j] = min(dp[i-1][j], dp[i-1][j-1], dp[i-1][j+1]) + dp[i][j], 還要處理邊緣的case

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        for (int i=1; i<triangle.size(); i++) {
            int ss = triangle[i].size();
            for (int j=0; j<ss; j++) {
                if (!j) 
                    triangle[i][j] += triangle[i-1][j];
                else if (j==ss-1) 
                    triangle[i][j] += triangle[i-1][j-1];
                else 
                    triangle[i][j] += min(triangle[i-1][j], 
                                          triangle[i-1][j-1]);
            }
        }
        return *min_element(triangle[triangle.size()-1].begin(),         
                            triangle[triangle.size()-1].end());
    }
};

1049. Last Stone Weight II Medium

474. Ones and Zeroes Medium

221. Maximal Square Medium

322. Coin Change Medium

1240. Tiling a Rectangle with the Fewest Squares Hard

174. Dungeon Game Hard

871. Minimum Number of Refueling Stops Hard

2.達到目標的方法總數dp(Distinct Ways)

Statement:Given a target find a number of distinct ways to reach the target.

Approach:Sum all possible ways to reach the current state.

routes[i] = routes[i-1] + routes[i-2], ... , + routes[i-k]

70. Climbing Stairs

class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n+1, 1);
        
        for (int i=2; i<=n; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
};

62. Unique Paths

f[i][j] = f[i][j-1]+f[i-1][j];

class Solution {
public:
    int uniquePaths(int m, int n) {
        // f[i][j] = f[i][j-1]+f[i-1][j];
        vector<vector<int>> dp(m, vector<int>(n, 0));
        dp[0][0] = 1; 
        for(int i=0; i<m; i++) {
            for (int j=0; j<n; j++) {
                if (i && j)
                    dp[i][j] = dp[i][j-1] + dp[i-1][j];
                else if (i)
                    dp[i][j] = dp[i-1][j];
                else if (j)
                    dp[i][j] = dp[i][j-1];
            }
        }
        
        return dp[m-1][n-1];
    }
};

1155. Number of Dice Rolls With Target Sum

dp[i][j] = dp[i-1][j-k] for k from 1 to f
有點像揹包的思路

class Solution {
public:
    int numRollsToTarget(int d, int f, int target) {
        // f[target] = sum(f[target-num[i]]);
        vector<vector<int>> dp(d+1, vector<int> (target+1));
        dp[0][0] = 1; int m = 1e9+7;
        
        for (int i=1; i<=d; i++) {
            for (int j=1; j<=target; j++) {
                for (int k=1; k<=f && k<=j; k++) {
                    dp[i][j] = dp[i][j]%m + dp[i-1][j-k]% m;
                }
            }
        }
        return dp[d][target] % m;
    }
};

https://leetcode.com/problems/number-of-dice-rolls-with-target-sum/discuss/805597/dp-solution-written-in-c%2B%2B(three-loop-and-two-loop)

688. Knight Probability in Chessboard

494. Target Sum Medium

377. Combination Sum IV Medium

935. Knight Dialer Medium

1223. Dice Roll Simulation Medium

416. Partition Equal Subset Sum Medium

808. Soup Servings Medium

790. Domino and Tromino Tiling Medium

801. Minimum Swaps To Make Sequences Increasing

673. Number of Longest Increasing Subsequence Medium

63. Unique Paths II Medium

576. Out of Boundary Paths Medium

1269. Number of Ways to Stay in the Same Place After Some Steps Hard

1220. Count Vowels Permutation Hard

3.區間合併型dp(Merging Intervals)

沒看懂 改天回來再搞
Statement: 給定一組數字,考慮到當前數字和從左側和右側可獲得的最佳值(可以為左右子樹),來找到問題的最佳解決方案。就是從小區間出發,然後合併小區間最優。

Approach: 找到每區間的所有最佳解決方案,並返回最佳答案

// from i to j
dp[i][j] = dp[i][k] + result[k] + dp[k+1][j]

// [0,...,n-l-1], [n-l, ..., n-1]

for(int l = 1; l<n; l++) {
   for(int i = 0; i<n-l; i++) {
       int j = i+l;
       for(int k = i; k<j; k++) {
           dp[i][j] = max(dp[i][j], dp[i][k] + result[k] + dp[k+1][j]);
       }
   }
}

1130. Minimum Cost Tree From Leaf Values

class Solution {
public:
    int mctFromLeafValues(vector<int>& arr) {
        int n = arr.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        vector<vector<int>> max_v(n,vector<int>(n,0));

// maxv[i][j] -> [i, ..., j] 的最大值
        for(int i = 0; i < n ; i ++)
            max_v[i][i] = arr[i];
        for(int d = 1 ; d < n ; d ++)
        {
            for(int i = 0 ; i + d < n ; i++)
            {
                int j = i + d;
                max_v[i][j] = max(max_v[i][j - 1],arr[j]);
            }
        }
// dp[i][j] : arr[i, ..., j]構成的樹所有非葉子節點的最小值
        for(int i = 0 ; i < n - 1 ; i ++)
            dp[i][i + 1] = arr[i] * arr[i + 1];
        for(int d = 2 ; d < n ; d ++)
        {
            for(int i = 0 ; i + d < n ; i ++)
            {
                int j = i + d;
                int cur_min = INT_MAX;
                for(int k = i ; k < j ; k ++)
                    cur_min = min(cur_min,dp[i][k] + dp[k + 1][j] + max_v[i][k]*max_v[k + 1][j]);
                dp[i][j] = cur_min; 
            }
        }
        return dp[0][n - 1];
    }
};

作者:T-SHLoRk
連結:https://www.acwing.com/solution/LeetCode/content/3996/
來源:AcWing
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

96. Unique Binary Search Trees Medium

1039. Minimum Score Triangulation of Polygon Medium

546. Remove Boxes Medium

1000. Minimum Cost to Merge Stones Medium

312. Burst Balloons Hard

375. Guess Number Higher or Lower II Medium

4.字串上的DP

Statement: 給定倆字串,得到某個結果

Approach: Most of the problems on this pattern requires a solution that can be accepted in O(n^2) complexity.

// i - indexing string s1
// j - indexing string s2
for (int i = 1; i <= n; ++i) {
   for (int j = 1; j <= m; ++j) {
       if (s1[i-1] == s2[j-1]) {
           dp[i][j] = /*code*/;
       } else {
           dp[i][j] = /*code*/;
       }
   }
}

一個字串的情況

for (int l = 1; l < n; ++l) {
   for (int i = 0; i < n-l; ++i) {
       int j = i + l;
       if (s[i] == s[j]) {
           dp[i][j] = /*code*/;
       } else {
           dp[i][j] = /*code*/;
       }
   }
}

5.決策型DP

The general problem statement for this pattern is forgiven situation decide whether to use or not to use the current state. So, the problem requires you to make a decision at a current state.

Statement:Given a set of values find an answer with an option to choose or ignore the current value.

Approach:If you decide to choose the current value use the previous result where the value was ignored; vice-versa, if you decide to ignore the current value use previous result where value was used.

// i - indexing a set of values
// j - options to ignore j values
for (int i = 1; i < n; ++i) {
   for (int j = 1; j <= k; ++j) {
       dp[i][j] = max({dp[i][j], dp[i-1][j] + arr[i], dp[i-1][j-1]});
       dp[i][j-1] = max({dp[i][j-1], dp[i-1][j-1] + arr[i], arr[i]});
   }
}

https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns#distinct-ways

6.揹包問題

y總的閆式dp分析法總結。

01揹包

#include <cstdio>
#include <vector>

using namespace std;

int n, m;
vector <vector<int>> dp;
vector<int> prices;
vector<int> cap;

void run() {
    for (int i=1; i<=n; i++) {
        for (int j=1; j<=m; j++) {
            if (j<cap[i-1]) dp[i][j] = dp[i-1][j];
            else dp[i][j] = max(dp[i-1][j], dp[i-1][j-cap[i-1]]+prices[i-1]);
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    prices = vector<int>(n);
    cap = vector<int>(n);
    dp = vector<vector<int>>(n+1, vector<int>(m+1));

    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }

    run();
    printf("%d", dp[n][m]);
}

滑動陣列改進

#include <cstdio>
#include <vector>

using namespace std;

int n, m;
vector<int> dp;
vector<int> prices;
vector<int> cap;

void run() {
    for (int i=1; i<=n; i++) {
        for (int j=m; j>=cap[i-1]; j--) {
            dp[j] = max(dp[j], dp[j-cap[i-1]]+prices[i-1]);
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    prices = vector<int>(n);
    cap = vector<int>(n);
    dp = vector<int> (m+1);

    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }

    run();
    printf("%d", dp[m]);
}

完全揹包問題

樸素O(nm^2) ,可把dp放在input處,小小優化。https://www.acwing.com/solution/content/10454/

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<vector<int>> dp;

void run() {
    for(int i=1; i<=n; i++)
        for (int j=1; j<=m; j++) 
            for (int k=0; cap[i-1]*k<=j; k++) // 注意是0開始
                dp[i][j] = max(dp[i][j], dp[i-1][j-cap[i-1]*k] + k*prices[i-1]);
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n);
    prices = vector<int>(n);
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

優化

dp[i][j]= max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);

dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - 2*v] + w, dp[i - 1][j - 3 * v] + 2 * w);
將每一項一一比對,我們可以得到下列狀態表示:
dp[i][j] = max(dp[i - 1][j], dp[i][j - v]+w);注意這裡是dp[i]不是dp[i-1]

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<vector<int>> dp;

void run() {
    for(int i=1; i<=n; i++) 
        for (int j=1; j<=m; j++) 
            if (j<cap[i-1])
                dp[i][j] = dp[i-1][j];
            else 
                dp[i][j] = max(dp[i-1][j], dp[i][j-cap[i-1]] + prices[i-1]);
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n);
    prices = vector<int>(n);
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

滾動陣列優化

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<int> dp;

void run() {
    for(int i=1; i<=n; i++) 
        for (int j=cap[i-1]; j<=m; j++) // 這次要從小到大
            dp[j] = max(dp[j], dp[j-cap[i-1]] + prices[i-1]);
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n);
    prices = vector<int>(n);
    dp = vector<int> (m+1);
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &cap[i], &prices[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[m]);
}

滑動陣列+input優化

#include <cstdio>
#include <vector>

using namespace std;

int n,m,v,w;
vector<int> dp;

int main() {
    scanf("%d %d", &n, &m);

    dp = vector<int> (m+1);
    
    for (int i=0; i<n; i++) {
        scanf("%d %d", &w, &v);
        for (int j=w; j<=m; j++) 
            dp[j] = max(dp[j], dp[j-w] + v);
    }
    
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[m]);
}

多重揹包問題

f[i,j] = Max(f[i-1,j], f[i-1,j-v]+w,f[i-1,j-2v]+2w,…,f[i-1,j-sv]+sw)
f[i,j-v]=Max(f[i-1,j-v], f[i-1,j-2v]+w,…, f[i-1,j-(s+1)v]+(s+1)w)
加上了數量的限制後,f[i,j]和f[i,j-v]的關係不好比較

#include <cstdio>
#include <vector>

using namespace std;

int n,m;
vector<int> prices;
vector<int> cap;
vector<int> nums;
vector<vector<int>> dp;

void run() {
    for(int i=1; i<=n; i++) 
        for (int j=1; j<=m; j++) 
            for (int k=0; k*cap[i]<=j && k<=nums[i]; k++) 
                dp[i][j] = max(dp[i][j], dp[i-1][j-k*cap[i]] + k*prices[i]); // 必須是dp[i]不是dp[i-1], dp[i][j-2*cap]+2*w 不一定比dp[i][j-cap]+w大。。
}


int main() {
    scanf("%d %d", &n, &m);
    
    cap = vector<int>(n+1);
    prices = vector<int>(n+1);
    nums = vector<int>(n+1);
    
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=1; i<=n; i++) {
        scanf("%d %d %d", &cap[i], &prices[i], &nums[i]);
    }
    
    run();
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

輸入優化

#include <cstdio>
#include <vector>

using namespace std;

int n,m,price,cap,num;
vector<vector<int>> dp;

int main() {
    scanf("%d %d", &n, &m);
    dp = vector<vector<int>> (n+1, vector<int> (m+1));
    
    for (int i=1; i<=n; i++) {
        scanf("%d %d %d", &cap, &price, &num);
        for (int j=1; j<=m; j++) {
            for (int k=0; k*cap<=j && k<=num; k++) 
                dp[i][j] = max(dp[i][j], dp[i-1][j-cap*k]+k*price);
        }
    }
    
    // for (int i=0; i<=n; i++){ for (int j=0; j<=m; j++) printf("%d ", dp[i][j]); printf("\n");}
    printf("%d", dp[n][m]);
}

利用二進位制壓縮,退化成0-1揹包問題

#include <cstdio>
#include <vector> 

using namespace std;
int n, m, cap, price, num;
vector<pair<int, int>> bins; 
vector<int> dp;


int main()
{
    scanf("%d %d", &n, &m);
    dp = vector<int> (m+1);
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d %d", &cap, &price, &num);
        
        // 二進位制優化
        for(int j=1; j<num; num -= j, j<<=1) bins.push_back({j*price, j*cap});
        if(num) bins.push_back({num*price, num*cap});

        // 0-1揹包的優化方法
        for (auto [price, v] : bins)
            for(int j=m; j>=v; j--)
                dp[j]=max(dp[j],dp[j-v]+price);
                
        bins.clear();
    }  
    printf("%d", dp[m]);
}

分組揹包問題

#include <cstdio>
#include <vector>

using namespace std;

int n,m,num;
vector<vector<int>> dp;

vector<vector<pair<int, int>>> groups;

void run() {
    for(int i=1; i<=n; i++) {
        for (int j=1; j<=m; j++) {
            dp[i][j] = dp[i-1][j]; // dp[i][j] = dp[i-1][j] 只發生一次!放到innermost迴圈會發生多次,覆蓋前面得到的最大值。
            for (auto [v, price] : groups[i-1]) {
                if (j>=v) dp[i][j] = max(dp[i][j], dp[i-1][j-v]+price);
            }
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    dp = vector<vector<int>> (n+1, vector<int>(m+1));
    
    for (int i=0; i<n; i++) {
        scanf("%d", &num);
        vector<pair<int, int>> vec(num);
        for (int j=0; j<num; j++) {
            int v, price;
            scanf("%d %d", &v, &price);
            
            vec[j].first=v;
            vec[j].second=price;
        }
        groups.emplace_back(move(vec));
    }
    run();
    printf("%d", dp[n][m]);
}

i只用到第i-1列,所以可用0-1揹包的移動陣列優化

#include <cstdio>
#include <vector>

using namespace std;

int n,m,num;
vector<int> dp;

vector<vector<pair<int, int>>> groups;

void run() {
    for(int i=1; i<=n; i++) {
        for (int j=m; j>=1; j--) {
            for (auto [v, price] : groups[i-1]) {
                if (j>=v) dp[j] = max(dp[j], dp[j-v]+price);
            }
        }
    }
}


int main() {
    scanf("%d %d", &n, &m);
    dp = vector<int>(m+1);
    
    for (int i=0; i<n; i++) {
        scanf("%d", &num);
        vector<pair<int, int>> vec(num);
        for (int j=0; j<num; j++) {
            int v, price;
            scanf("%d %d", &v, &price);
            
            vec[j].first=v;
            vec[j].second=price;
        }
        groups.emplace_back(move(vec));
    }
    run();
    printf("%d", dp[m]);
}