AcWing 1021. 货币系统 原题链接
给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
输入格式
第一行,包含两个整数n和m。
接下来n行,每行包含一个整数,表示一种货币的面值。
输出格式
共一行,包含一个整数,表示方案数。
数据范围
n≤15,m≤3000n≤15,m≤3000
输入样例:
3 10
1
2
5
输出样例:
10
题目思路

    public static void main(String[] args) {
        int n = in.nextInt();
        int m = in.nextInt();
        long[] dp = new long[m + 1];
        int[] arr = new int[n + 1];
        for (int i = 1; i <= n; i++) arr[i] = in.nextInt();
        dp[0] = 1L;
        for (int i = 1; i < n + 1; i++) {
            for (int j = arr[i]; j <= m; j++) {
                dp[j] += dp[j - arr[i]];
            }
        }
        out.println(dp[m]);
        out.flush();
        out.close();
    }
AcWing 532. 货币系统 原题链接
在网友的国度中共有 nn 种不同面额的货币,第 ii 种货币的面额为 a[i]a[i],你可以假设每一种货币都有无穷多张。
为了方便,我们把货币种数为 nn、面额数组为 a[1..n]a[1..n] 的货币系统记作 (n,a)(n,a)。
在一个完善的货币系统中,每一个非负整数的金额 xx 都应该可以被表示出,即对每一个非负整数 xx,都存在 nn 个非负整数 t[i]t[i] 满足 a[i]×t[i]a[i]×t[i] 的和为 xx。
然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额 xx 不能被该货币系统表示出。
例如在货币系统 n=3, a=[2,5,9]n=3, a=[2,5,9] 中,金额 1,31,3 就无法被表示出来。
两个货币系统 (n,a)(n,a) 和 (m,b)(m,b) 是等价的,当且仅当对于任意非负整数 xx,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。
现在网友们打算简化一下货币系统。
他们希望找到一个货币系统 (m,b)(m,b),满足 (m,b)(m,b) 与原来的货币系统 (n,a)(n,a) 等价,且 mm 尽可能的小。
他们希望你来协助完成这个艰巨的任务:找到最小的 mm。
输入格式
输入文件的第一行包含一个整数 TT,表示数据的组数。
接下来按照如下格式分别给出 TT 组数据。
每组数据的第一行包含一个正整数 nn。
接下来一行包含 nn 个由空格隔开的正整数 a[i]a[i]。
输出格式
输出文件共有 TT 行,对于每组数据,输出一行一个正整数,表示所有与 (n,a)(n,a) 等价的货币系统 (m,b)(m,b) 中,最小的 mm。
数据范围
1≤n≤1001≤n≤100, 1≤a[i]≤250001≤a[i]≤25000, 1≤T≤201≤T≤20
输入样例:
2 
4 
3 19 10 6 
5 
11 29 13 19 17 
输出样例:
2
5
题目思路

    public static void main(String[] args) {
        int t = in.nextInt();
        while (t-- > 0) {
            int n = in.nextInt();
            int res = 0;
            int[] arr = new int[n + 1];
            for (int i = 1; i < n + 1; i++) arr[i] = in.nextInt();
            Arrays.sort(arr, 1, n + 1);
            //最大值就是背包容量
            int[] dp = new int[arr[n] + 1];
            dp[0] = 1;
            for (int i = 1; i < n + 1; i++) {
                //判断[1,i-1]有没有能够组成arr[i]的
                if (dp[arr[i]] == 0) res++;
                //利用arr[i]更新后面的方案数
                for (int j = arr[i]; j <= arr[n]; j++) {
                    dp[j] += dp[j - arr[i]];
                }
            }
            out.println(res);
        }
        out.flush();
        out.close();
    }
AcWing 7. 混合背包问题 原题链接
有 NN 种物品和一个容量是 VV 的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 sisi 次(多重背包);
每种体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行三个整数 vi,wi,sivi,wi,si,用空格隔开,分别表示第 ii 种物品的体积、价值和数量。
- si=−1si=−1 表示第 ii 种物品只能用1次;
- si=0si=0 表示第 ii 种物品可以用无限次;
- si>0si>0 表示第 ii 种物品可以使用 sisi 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000 0<vi,wi≤10000<vi,wi≤1000 −1≤si≤1000−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
    private static class Goods {
        private int volume;
        private int value;
        private int type;
        public Goods(int volume, int value, int type) {
            this.volume = volume;
            this.value = value;
            this.type = type;
        }
    }
    public static void main(String[] args) {
        int n = in.nextInt();
        int m = in.nextInt();
        Goods[] goods = new Goods[n + 1];
        int[] dp = new int[m + 1];
        for (int i = 1; i < n + 1; i++) {
            goods[i] = new Goods(in.nextInt(), in.nextInt(), in.nextInt());
        }
        for (int i = 1; i < n + 1; i++) {
            int s = goods[i].type;
            if (s == 0) {
                for (int j = goods[i].volume; j <= m; j++) {
                    dp[j] = Math.max(dp[j], dp[j - goods[i].volume] + goods[i].value);
                }
            } else {
                //01背包看成特殊的多重背包
                if (s == -1) s = 1;
                //二进制打包
                for (int j = 1; s >= j; s -= j, j <<= 1) {
                    //01背包过程
                    for (int k = m; k >= j * goods[i].volume; k--) {
                        dp[k] = Math.max(dp[k], dp[k - j * goods[i].volume] + j * goods[i].value);
                    }
                }
                //打包剩下的
                if (s > 0) {
                    for (int k = m; k >= s * goods[i].volume; k--) {
                        dp[k] = Math.max(dp[k], dp[k - s * goods[i].volume] + s * goods[i].value);
                    }
                }
            }
        }
        out.println(dp[m]);
        out.flush();
        out.close();
    }
AcWing 10. 有依赖的背包问题 原题链接
有 NN 个物品和一个容量是 VV 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如下图所示:

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。
每件物品的编号是 ii,体积是 vivi,价值是 wiwi,依赖的父节点编号是 pipi。物品的下标范围是 1…N1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,VN,V,用空格隔开,分别表示物品个数和背包容量。
接下来有 NN 行数据,每行数据表示一个物品。 第 ii 行有三个整数 vi,wi,pivi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。 如果 pi=−1pi=−1,表示根节点。 数据保证所有物品构成一棵树。
输出格式
输出一个整数,表示最大价值。
数据范围
1≤N,V≤1001≤N,V≤100 1≤vi,wi≤1001≤vi,wi≤100
父节点编号范围:
- 内部结点:1≤pi≤N1≤pi≤N;
- 根节点 pi=−1pi=−1;
输入样例
5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2
输出样例:
11
题目思路

    static List<List<Integer>> graph = new ArrayList<>();
    static Goods[] goods;
    static int[][] dp;
    static int n;
    static int m;
    private static class Goods{
        private int volume;
        private int value;
        public Goods(int volume, int value) {
            this.volume = volume;
            this.value = value;
        }
    }
    private static void add(int a, int b) {
        graph.get(b).add(a);
    }
    private static void dfs(int u) {
        List<Integer> list = graph.get(u);
        for (int x : list) {
            dfs(x);
            //根节点是必选的,说剩下体积就是m-goods[u].volume
            //分组背包过程
            for (int j = m - goods[u].volume; j >= 0; j--) {
                //枚举子树所占[0,j]体积的方案
                for (int k = 0; k <= j; k++) {
                    dp[u][j] = Math.max(dp[u][j], dp[u][j - k] + dp[x][k]);
                }
            }
        }
        //每个方案需要加上本身选择root的价值
        for (int i = m; i >= goods[u].volume; i--) {
            dp[u][i] = dp[u][i - goods[u].volume] + goods[u].value;
        }
        //对于背包体积装不下root的情况,设置为0
        for (int i = 0; i < goods[u].volume; i++) dp[u][i] = 0;
    }
    public static void main(String[] args) {
        n = in.nextInt();
        m = in.nextInt();
        goods = new Goods[n + 1];
        dp = new int[n + 1][m + 1];
        for (int i = 0; i < n + 1; i++) graph.add(new ArrayList<>());
        int root = -1;
        for (int i = 1; i < n + 1; i++) {
            int volume = in.nextInt();
            int value = in.nextInt();
            int dependence = in.nextInt();
            goods[i] = new Goods(volume, value);
            if (dependence == -1) {
                root = i;
                continue;
            }
            add(i, dependence);
        }
        dfs(root);
        out.println(dp[root][m]);
        out.flush();
        out.close();
    }
AcWing 11. 背包问题求方案数 原题链接
有 NN 件物品和一个容量是 VV 的背包。每件物品只能使用一次。
第 ii 件物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7109+7 的结果。
输入格式
第一行两个整数,N,VN,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 NN 行,每行两个整数 vi,wivi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7109+7 的结果。
数据范围
0<N,V≤10000<N,V≤1000 0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
题目思路

朴素算法:
    private static void function1() {
        int n = in.nextInt();
        int m = in.nextInt();
        int mod = (int)(1e9 + 7);
        Goods[] goods = new Goods[n + 1];
        for (int i = 1; i < n + 1; i++) goods[i] = new Goods(in.nextInt(), in.nextInt());
        int[][] dp = new int[n + 1][m + 1];
        int[][] g = new int[n + 1][m + 1];
        for (int i = 0; i < n + 1; i++) Arrays.fill(dp[i], Integer.MIN_VALUE);
        //从前i个选,体积为0最大值都为0
        for (int i = 0; i < n + 1; i++) dp[i][0] = 0;
        //从前i个选,体积为0,最优选法都是不选,即为一种
        for (int j = 0; j < m + 1; j++) g[0][j] = 1;
        for (int i = 1; i < n + 1; i++) {
            for (int j = 0; j < m + 1; j++) {
                int max = dp[i - 1][j];
                if (j >= goods[i].volume) {
                    max = Math.max(max, dp[i - 1][j - goods[i].volume] + goods[i].value);
                }
                if (max == dp[i - 1][j]) g[i][j] = (g[i][j] + g[i - 1][j]) % mod;
                if (j >= goods[i].volume && max == dp[i - 1][j - goods[i].volume] + goods[i].value) g[i][j] = (g[i][j] + g[i - 1][j - goods[i].volume]) % mod;
                dp[i][j] = max;
            }
        }
        int max = 0;
        for (int j = 0; j < m + 1; j++) max = Math.max(max, dp[n][j]);
        int res = 0;
        for (int j = 0; j < m + 1; j++) {
            if (dp[n][j] == max) res = (res + g[n][j]) % mod;
        }
        out.println(res);
        out.flush();
        out.close();
    }
空间优化写法:
    private static void function2() {
        int n = in.nextInt();
        int m = in.nextInt();
        int mod = (int)(1e9 + 7);
        Goods[] goods = new Goods[n + 1];
        for (int i = 1; i < n + 1; i++) goods[i] = new Goods(in.nextInt(), in.nextInt());
        int[] dp = new int[m + 1]; int[] g = new int[m + 1];
        Arrays.fill(dp, Integer.MIN_VALUE);
        dp[0] = 0; g[0] = 1;
        for (int i = 1; i < n + 1; i++) {
            for (int j = m; j >= goods[i].volume; j--) {
                int max = Math.max(dp[j], dp[j - goods[i].volume] + goods[i].value);
                if (max != dp[j]) g[j] = 0;
                if (max == dp[j - goods[i].volume] + goods[i].value) g[j] = (g[j] + g[j - goods[i].volume]) % mod;
                dp[j] = max;
            }
        }
        int max = 0;
        int res = 0;
        for (int j = 0; j < m + 1; j++) max = Math.max(max, dp[j]);
        for (int j = 0; j < m + 1; j++) {
            if (dp[j] == max) {
                res = (res + g[j]) % mod;
            }
        }
        out.println(res);
        out.flush();
        out.close();
    }
AcWing 734. 能量石 原题链接
岩石怪物杜达生活在魔法森林中,他在午餐时收集了N块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为SiSi秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i 块能量石最初包含EiEi单位的能量,并且每秒将失去LiLi单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至0。
请问杜达通过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数T,表示共有T组测试数据。
每组数据第一行包含整数N,表示能量石的数量。
接下来N行,每行包含三个整数Si,Ei,LiSi,Ei,Li。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为“Case #x: y”,其中x是组别编号(从1开始),y是可以获得的最大能量值。
数据范围
1≤T≤101≤T≤10, 1≤N≤1001≤N≤100, 1≤Si≤1001≤Si≤100, 1≤Ei≤1051≤Ei≤105, 0≤Li≤1050≤Li≤105
输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500
样例解释
在样例#1中,有N = 4个宝石。杜达可以选择的一个吃石头顺序是:
- 吃第四块石头。这需要5秒,并给他80单位的能量。
- 吃第二块石头。这需要5秒,并给他5单位的能量(第二块石头开始时具有30单位能量,5秒后失去了25单位的能量)。
- 吃第三块石头。这需要100秒,并给他20单位的能量(第三块石头开始时具有30单位能量,10秒后失去了10单位的能量)。
- 吃第一块石头。这需要20秒,并给他0单位的能量(第一块石头以10单位能量开始,110秒后已经失去了所有的能量)。
他一共获得了105单位的能量,这是能获得的最大值,所以答案是105。
在样本案例#2中,有N = 3个宝石。
无论杜达选择吃哪块石头,剩下的两个石头的能量都会耗光。
所以他应该吃第三块石头,给他提供8单位的能量。
在样本案例#3中,有N = 2个宝石。杜达可以:
- 吃第一块石头。这需要12秒,并给他300单位的能量。
- 吃第二块石头。这需要5秒,并给他200单位的能量(第二块石头随着时间的推移不会失去任何能量!)。
所以答案是500。
题目思路

    private static class Stone implements Comparable<Stone>{
        private int time;
        private int energy;
        private int decrease;
        public Stone(int time, int energy, int decrease) {
            this.time = time;
            this.energy = energy;
            this.decrease = decrease;
        }
        @Override
        public int compareTo(Stone o) {
            return Integer.compare(this.time * o.decrease, this.decrease * o.time);
        }
    }
    public static void main(String[] args) {
        int t = in.nextInt();
        for (int T = 1; T <= t; T++) {
            int n = in.nextInt();
            int m = 0;
            Stone[] stones = new Stone[n + 1];
            for (int i = 1; i < n + 1; i++) {
                int s = in.nextInt();
                int e = in.nextInt();
                int l = in.nextInt();
                stones[i] = new Stone(s, e, l);
                m += s;
            }
            int[] dp = new int[m + 1];
            Arrays.fill(dp, Integer.MIN_VALUE);
            dp[0] = 0;
            Arrays.sort(stones, 1, n + 1);
            for (int i = 1; i < n + 1; i++) {
                for (int j = m; j >= stones[i].time; j--) {
                    //注意能量是会随着时间变短的,实际退减时间为(j-stones[i].time)而不是j
                    //因为石头在吃到的瞬间就不会退减了,stones[i].time只是消化时间
                    //能量不能递减到小于0
                    int decreaseTime = j - stones[i].time;
                    dp[j] = Math.max(dp[j], dp[j - stones[i].time] + Math.max(0, stones[i].energy -  decreaseTime * stones[i].decrease));
                }
            }
            int res = 0;
            for (int j = 0; j <= m; j++) res = Math.max(res, dp[j]);
            out.println("Case #" + T +": " + res);
        }
        out.flush();
        out.close();
    }

