揹包問題大合集

翰林猿發表於2024-07-07

dp揹包3步曲

1.確定dp[i] [v]的含義(一維的話是dp[v]) :在 0…i 的物品中,體積為 v 的揹包中,能夠拿到的最大價值為 dp[i] [v]。
2.求關係式
    不拿物品:(物品數量減少)
        一維:dp[v] 
        二維:dp[i] [v] = dp[i-1] [v]
    拿:(物品數量減少,揹包體積減物品體積)
        一維:dp[v-weight[i]] 
        二維:dp[i] [v] = dp[i-1] [v-weight[i]]

一維揹包:
//遍歷物品的數量N
    for (int i = 1; i <= n; i++) {
    //遍歷揹包的容量V,但是從大到小進行遍歷,如果揹包容量大於物品體積就拿
        for (int j = capacity; j >= weights[i]; j--) {
            //不拿(揹包體積不變)和拿(揹包體積減少再加上物品價值),進行比大小,取大值。
            dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
        }
    }

二維揹包:

//遍歷物品的數量N

for(int i = 1;i<= n ; i++){
    //遍歷揹包的容量V
    for(int j = 0; j <= c ; j++ ){
        //如果揹包體積小於物品的體積,放不下就不拿了
        if(j<v[i]){
            dp[i][j] = dp[i-1][j];
        }else{
        //不拿(物品數量-1)和拿(物品數量-1,並且揹包體積減少再加上物品價值),進行比大小,取大值。
        dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        }
    

    }

}

二維揹包

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int numCases = sc.nextInt();  // Number of test cases

        for (int caseNum = 0; caseNum < numCases; caseNum++) {
            int numItems = sc.nextInt();  // Number of items
            int capacity = sc.nextInt();  // Maximum capacity of the knapsack
            int limit = sc.nextInt();     // Maximum weight limit

            int[] values = new int[numItems + 1];
            int[] weights = new int[numItems + 1];
            int[] profits = new int[numItems + 1];

            for (int i = 1; i <= numItems; i++) {
                values[i] = sc.nextInt();  // Value of the item
                weights[i] = sc.nextInt(); // Weight of the item
                profits[i] = sc.nextInt(); // Profit of the item
            }

            int[][] dp = new int[capacity + 1][limit + 1];  // DP table

            for (int i = 1; i <= numItems; i++) {
                for (int j = capacity; j >= values[i]; j--) {
                    for (int k = limit; k >= weights[i]; k--) {
                        dp[j][k] = Math.max(dp[j][k], dp[j - values[i]][k - weights[i]] + profits[i]);
                    }
                }
            }

            System.out.println(dp[capacity][limit]);
        }

        sc.close();
    }
}



如果每個物品只能用一次,反向更新是必要的;如果每個物品可以用多次,則可以正向更新。

01揹包

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int numCases = sc.nextInt();  // Number of test cases

        for (int caseNum = 0; caseNum < numCases; caseNum++) {
            int numItems = sc.nextInt();  // Number of items
            int capacity = sc.nextInt();  // Maximum capacity of the knapsack
            int limit = sc.nextInt();     // Maximum weight limit

            int[] values = new int[numItems + 1];
            int[] weights = new int[numItems + 1];
            int[] profits = new int[numItems + 1];

            for (int i = 1; i <= numItems; i++) {
                values[i] = sc.nextInt();  // Value of the item
                weights[i] = sc.nextInt(); // Weight of the item
                profits[i] = sc.nextInt(); // Profit of the item
            }

            int[][] dp = new int[capacity + 1][limit + 1];  // DP table

            for (int i = 1; i <= numItems; i++) {
                for (int j = capacity; j >= values[i]; j--) {
                    for (int k = limit; k >= weights[i]; k--) {
                        dp[j][k] = Math.max(dp[j][k], dp[j - values[i]][k - weights[i]] + profits[i]);
                    }
                }
            }

            System.out.println(dp[capacity][limit]);
        }

        sc.close();
    }
}



多重揹包

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int nt = sc.nextInt();

        for (int t = 0; t < nt; t++) {
            int n = sc.nextInt(); // 物品種類
            int c = sc.nextInt(); // 揹包容量

            int[] w = new int[n + 1];
            int[] v = new int[n + 1];
            int[] m = new int[n + 1];

            for (int i = 1; i <= n; i++) { // 索引從1開始
                w[i] = sc.nextInt(); // 物品重量
                v[i] = sc.nextInt(); // 物品價值
                m[i] = sc.nextInt(); // 物品個數
            }

            int[] dp = new int[c + 1];

            for (int i = 1; i <= n; i++) {      // 遍歷物品
                if (m[i] * w[i] >= c) {         // 如果物品數量無限多(足夠多),相當於完全揹包
                    for (int j = w[i]; j <= c; j++) {
                        dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
                    }
                } else {                                // 如果物品數量有限,先用二進位制最佳化,再當多重揹包做,減少迴圈次數
                    int num = m[i];                       //num表示物品的剩餘數量
                    for (int k = 1; num > 0; k <<= 1) {     //k表示當前二進位制位的值,當前物品數量>0就說明可以進行二進位制最佳化
                        //取小的,確保每次取的物品數量不超過實際剩餘數量,在k變大時,可能會超過num,這時就需要取實際剩餘的num而不是k。
                        int cnt = Math.min(k, num);         //cnt表示取出的物品數量
                        num -= cnt;                        //減去已從揹包分出去的物品
                        for (int j = c; j >= cnt * w[i]; j--) {
                            dp[j] = Math.max(dp[j], dp[j - cnt * w[i]] + cnt * v[i]);
                        }
                    }
                }
            }

            System.out.println(dp[c]); // 輸出結果
        }
        sc.close();
    }
}


最長公共子序列:

import java.util.Scanner;

public class Main {

    static char[] x;
    static char[] y;
    static int[][] dp; // 字串 X 和字串 Y 的最長公共子序列的長度。
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        sc.nextLine(); // 吸收換行符
    
        for (int i = 1; i <= t; i++) {
            x = sc.nextLine().toCharArray();
            y = sc.nextLine().toCharArray();
    
            // 初始化dp陣列,也就是x,y陣列的長度
            int xl = x.length;
            int yl = y.length;
    
            dp = new int[xl + 1][yl + 1]; // 初始化dp陣列大小
    
            // 迴圈陣列從1開始,不用加dp[][],因為已經初始化了
            for (int j = 1; j <= xl; j++) { 
                for (int k = 1; k <= yl; k++) {
                    // 如果上一個字元相同,就計算出當前值,更新當前的公共子序列長度
                    if (x[j - 1] == y[k - 1]) {
                        dp[j][k] = dp[j - 1][k - 1] + 1;
                    } else {
                        // 如果不相同,在之前算出來的答案找最適合的,取公共子序列的(二維方向的)前一位進行對比,取大的作為最長公共子序列。
                        dp[j][k] = Math.max(dp[j][k - 1], dp[j - 1][k]);
                    }
                }
            }
            System.out.println(dp[xl][yl]);
        }
    
        sc.close(); // 關閉讀寫流
    }

}

最長上升子序列:

核心思路:透過動態規劃的方法,用 dp[i] 表示以 nums[i] 結尾的最長上升子序列的長度,遍歷陣列,更新每個位置的 dp[i] 值,同時記錄整個陣列的最長上升子序列長度 maxans

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int size = scanner.nextInt();
        int[] numbers = new int[size];
   // 讀取輸入陣列
    for (int i = 0; i < size; i++) {
        numbers[i] = scanner.nextInt();
    }

    int result = lengthOfLIS(numbers);
    System.out.println(result);
}

public static int lengthOfLIS(int[] nums) {
    // dp[i] 表示下標i在 nums[i] 時,也就是0-i的最長上升子序列的長度
    int[] dp = new int[nums.length];
    dp[0] = 1; // 初始化,每個元素自身構成一個長度為 1 的子序列
    int maxans = 1; // 記錄最長上升子序列的長度
    //外層迴圈從1開始遍歷每個元素,計算以該元素結尾的最長上升子序列的長度。
    for (int i = 1; i < nums.length; i++) {
        dp[i] = 1; // 也就是初始化dp的每一個元素為1,可以寫到外面
        for (int j = 0; j < i; j++) {
        	// 如果 nums[i] 大於 nums[j],則可以將 nums[i] 加入到以 nums[j] 結尾的子序列中
            if (nums[i] > nums[j]) {
                //既然要更新當前下標i位置的最長上升子序列的長度dp,那就是拿上一個已經算出來的值+1更新,當然了要進行對比取最大值,如果當前的dp[i]比dp[j]還大,那就還是使用當前的。
                dp[i] = Math.max(dp[i], dp[j] + 1);		
            }
        }
        // 更新最長上升子序列的長度
        maxans = Math.max(maxans, dp[i]);
    }

    return maxans;
}

超市搞活動(01揹包)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int getMax(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

int knapsack(int capacity, int numItems, int profits[], int weights[]) {
    vector<int> dp(capacity + 1, 0);

    for (int i = 0; i < numItems; i++) {
        for (int j = weights[i]; j <= capacity; j++) {
            dp[j] = getMax(dp[j], dp[j - weights[i]] + profits[i]);
        }
    }

    return dp[capacity];
}

int main() {
    int capacity, numItems;

    while (cin >> capacity >> numItems) {
        int profits[numItems], weights[numItems];

        for (int i = 0; i < numItems; i++) {
            cin >> profits[i] >> weights[i];
        }

        int result = knapsack(capacity, numItems, profits, weights);
        cout << result << endl;
    }

    return 0;
}

相關文章