傅芳杰

个人博客

You Deserve The Best


背包模型3

AcWing 12. 背包问题求具体方案 原题链接

有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。

第 ii 件物品的体积是 vivi,价值是 wiwi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N1…N。

输入格式

第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。

输出格式

输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N1…N。

数据范围

0<N,V≤10000<N,V≤1000 0<vi,wi≤10000<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 6

输出样例:

1 4

题目思路

可以记录每一次的具体选择

由于这个题目需要输出字典序最小的选择,从前往后遍历时每次选择有三种情况:

  1. 必须选择
  2. 必须不选择
  3. 可选可不选时:必须选择

6twGMn.png

朴素写法:

    private static void function1() {
        int n = in.nextInt();
        int m = in.nextInt();
        Goods[] goods = new Goods[n + 2];
        int[][] dp = new int[n + 2][m + 1];
        int[][] path = new int[n + 2][m + 1];
        for (int i = 1; i < n + 1; i++) goods[i] = new Goods(in.nextInt(), in.nextInt());
        for (int i = n; i >= 1; i--) {
            for (int j = 0; j <= m; j++) {
                dp[i][j] = dp[i + 1][j];
                if (j >= goods[i].volume && dp[i][j] <= dp[i + 1][j - goods[i].volume] + goods[i].value) {
                    dp[i][j] = dp[i + 1][j - goods[i].volume] + goods[i].value;
                    path[i][j] = 1;
                }
            }
        }
        int c = m;
        for (int i = 1; i < n + 1; i++) {
            if (path[i][c] == 1) {
                out.print(i + " ");
                c -= goods[i].volume;
            }
        }
        out.flush();
        out.close();
    }

空间优化写法:

    private static void function2() {
        int n = in.nextInt();
        int m = in.nextInt();
        Goods[] goods = new Goods[n + 2];
        int[] dp = new int[m + 1];
        int[][] path = new int[n + 2][m + 1];
        for (int i = 1; i < n + 1; i++) goods[i] = new Goods(in.nextInt(), in.nextInt());
        for (int i = n; i >= 1; i--) {
            for (int j = m; j >= 0; j--) {
                if (j >= goods[i].volume && dp[j] <= dp[j - goods[i].volume] + goods[i].value) {
                    dp[j] = dp[j - goods[i].volume] + goods[i].value;
                    path[i][j] = 1;
                }
            }
        }
        int c = m;
        for (int i = 1; i < n + 1; i++) {
            if (path[i][c] == 1) {
                out.print(i + " ");
                c -= goods[i].volume;
            }
        }
        out.flush();
        out.close();
    }

AcWing 1013. 机器分配 原题链接

总公司拥有M台 相同 的高效设备,准备分给下属的N个分公司。

各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。

问:如何分配这M台设备才能使国家得到的盈利最大?

求出最大盈利值。

分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数M。

输入格式

第一行有两个数,第一个数是分公司数N,第二个数是设备台数M;

接下来是一个N*M的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。

输出格式

第一行输出最大盈利值;

接下N行,每行有2个数,即分公司编号和该分公司获得设备台数。

答案不唯一,输入任意合法方案即可。

数据范围

1≤N≤101≤N≤10, 1≤M≤151≤M≤15

输入样例:

3 3
30 40 50
20 30 50
20 25 30

输出样例:

70
1 1
2 1
3 1

题目思路

按公司分组,总体积就是机器总数,给公司分配1,2,3…j台机器看成同一分组中不同的物品,公司获利看成是物品的价值

整个物品就能转化成分组背包问题

朴素写法:

    private static void function1() {
        int n = in.nextInt();
        int m = in.nextInt();
        Goods[] goods = new Goods[n + 2];
        int[][] dp = new int[n + 2][m + 1];
        int[][] path = new int[n + 2][m + 1];
        for (int i = 1; i < n + 1; i++) goods[i] = new Goods(in.nextInt(), in.nextInt());
        for (int i = n; i >= 1; i--) {
            for (int j = 0; j <= m; j++) {
                dp[i][j] = dp[i + 1][j];
                if (j >= goods[i].volume && dp[i][j] <= dp[i + 1][j - goods[i].volume] + goods[i].value) {
                    dp[i][j] = dp[i + 1][j - goods[i].volume] + goods[i].value;
                    path[i][j] = 1;
                }
            }
        }
        int c = m;
        for (int i = 1; i < n + 1; i++) {
            if (path[i][c] == 1) {
                out.print(i + " ");
                c -= goods[i].volume;
            }
        }
        out.flush();
        out.close();
    }

空间优化写法:

    private static void function2() {
        int n = in.nextInt();
        int m = in.nextInt();
        Goods[] goods = new Goods[n + 2];
        int[] dp = new int[m + 1];
        int[][] path = new int[n + 2][m + 1];
        for (int i = 1; i < n + 1; i++) goods[i] = new Goods(in.nextInt(), in.nextInt());
        for (int i = n; i >= 1; i--) {
            for (int j = m; j >= 0; j--) {
                if (j >= goods[i].volume && dp[j] <= dp[j - goods[i].volume] + goods[i].value) {
                    dp[j] = dp[j - goods[i].volume] + goods[i].value;
                    path[i][j] = 1;
                }
            }
        }
        int c = m;
        for (int i = 1; i < n + 1; i++) {
            if (path[i][c] == 1) {
                out.print(i + " ");
                c -= goods[i].volume;
            }
        }
        out.flush();
        out.close();
    }

AcWing 426. 开心的金明 原题链接

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 NN 元钱就行”。

今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的 NN 元。

于是,他把每件物品规定了一个重要度,分为 55 等:用整数 1∼51∼5 表示,第 55 等最重要。

他还从因特网上查到了每件物品的价格(都是整数元)。

他希望在不超过 NN 元(可以等于 NN 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。 

设第 jj 件物品的价格为 v[j]v[j],重要度为 w[j]w[j],共选中了 kk 件物品,编号依次为 j1,j2,…,jkj1,j2,…,jk,则所求的总和为: 

v[j1]×w[j1]+v[j2]×w[j2]+…+v[jk]×w[jk]v[j1]×w[j1]+v[j2]×w[j2]+…+v[jk]×w[jk]

请你帮助金明设计一个满足要求的购物单。

输入格式

输入文件的第 11 行,为两个正整数 NN 和 mm,用一个空格隔开。(其中 NN 表示总钱数,mm 为希望购买物品的个数) 

从第 22 行到第 m+1m+1 行,第 jj 行给出了编号为 j−1j−1 的物品的基本数据,每行有 22 个非负整数 vv 和 pp。(其中 vv 表示该物品的价格,pp 表示该物品的重要度)

输出格式

输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(数据保证结果不超过 108108)。

数据范围

1≤N<300001≤N<30000, 1≤m<251≤m<25, 0≤v≤100000≤v≤10000, 1≤p≤51≤p≤5

输入样例:

1000 5
800 2
400 5
300 5
400 3
200 2

输出样例:

3900
    public static void main(String[] args) {
        int m = in.nextInt();
        int n = in.nextInt();
        int[] dp = new int[m + 1];
        Goods[] goods = new Goods[n + 1];
        for (int i = 1; i < n + 1; i++) {
            int price = in.nextInt();
            goods[i] = new Goods(price, in.nextInt() * price);
        }
        for (int i = 1; i < n + 1; i++) {
            for (int j = m; j >= goods[i].price; j--) {
                dp[j] = Math.max(dp[j], dp[j - goods[i].price] + goods[i].value);
            }
        }
        out.println(dp[m]);
        out.flush();
        out.close();
    }

AcWing 487. 金明的预算方案 原题链接

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。

更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。

今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

6twJrq.png

如果要买归类为附件的物品,必须先买该附件所属的主件。

每个主件可以有0个、1个或2个附件。

附件不再有从属于自己的附件。

金明想买的东西很多,肯定会超过妈妈限定的N元。

于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。

他还从因特网上查到了每件物品的价格(都是10元的整数倍)。

他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,…,jkj1,j2,…,jk,则所求的总和为:

v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中*为乘号)

请你帮助金明设计一个满足要求的购物单。

输入格式

输入文件的第1行,为两个正整数,用一个空格隔开:N m,其中N表示总钱数,m为希望购买物品的个数。

从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数v p q,其中v表示该物品的价格,p表示该物品的重要度(1~5),q表示该物品是主件还是附件。

如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号。

输出格式

输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。

数据范围

N<32000,m<60,v<10000N<32000,m<60,v<10000

输入样例:

1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0

输出样例:

2200

题目思路

每件物品的组件和附件的组合选择看成分组中的一种物品,问题就能转化成为分组背包问题

    private static class Combination {
        private int price;
        private int value;

        public Combination(int price, int value) {
            this.price = price;
            this.value = value;
        }
    }
    public static void main(String[] args) {
        int m = in.nextInt();
        int n = in.nextInt();
        List<Combination>[] combinations = new ArrayList[n + 1];
        for (int i = 0; i < n + 1; i++) combinations[i] = new ArrayList<>();
        int[] dp = new int[m + 1];
        for (int i = 1; i < n + 1; i++) {
            int price = in.nextInt();
            int weight = in.nextInt();
            int p = in.nextInt();
            if (p != 0) {
                List<Combination> list = new ArrayList<>(combinations[p]);
                List<Combination> clist = combinations[p];
                for (Combination c : list) {
                    clist.add(new Combination(c.price + price, c.value + price * weight));
                }
            }else {
                combinations[i].add(new Combination(price, price * weight));
            }
        }
        for (int i = 1; i < n + 1; i++) {
            for (int j = m; j >= 0; j--) {
                for (int k = 0; k < combinations[i].size(); k++) {
                    Combination c = combinations[i].get(k);
                    if (j >= c.price) {
                        dp[j] = Math.max(dp[j], dp[j - c.price] + c.value);
                    }
                }
            }
        }
        out.println(dp[m]);
        out.flush();
        out.close();
    }

二进制枚举写法:

    //二进制枚举
    private static void function2() {
        int m = in.nextInt();
        int n = in.nextInt();
        int[] dp = new int[m + 1];
        Accessories[] master = new Accessories[n + 1];
        List<Accessories>[] accessories = new ArrayList[n + 1];
        for (int i = 0; i <= n; i++) accessories[i] = new ArrayList<>();
        for (int i = 1; i < n + 1; i++) {
            int p = in.nextInt();
            int w = in.nextInt();
            int q = in.nextInt();
            if (q != 0) accessories[q].add(new Accessories(p, w * p));
            else master[i] = new Accessories(p, p * w);
        }
        for (int i = 1; i < n + 1; i++) {
            if (master[i] == null) continue;
            for (int j = m; j >= 0; j--) {
                for (int x = 0; x < (1 << accessories[i].size()); x++) {
                    int price = master[i].price;
                    int value = master[i].value;
                    for (int k = 0; k < accessories[i].size(); k++) {
                        if ((1 & (x >> k)) == 1) {
                            price += accessories[i].get(k).price;
                            value += accessories[i].get(k).value;
                        }
                    }
                    if (j >= price) dp[j] = Math.max(dp[j], dp[j - price] + value);
                }
            }
        }
        out.println(dp[m]);
        out.flush();
        out.close();
    }