分组背包:多个物品分组,每组只能取1件
每一组的物品都可能性展开就可以,时间复杂度为O(物品的数量*背包的容量)
分组背包
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;public class Main {public static int MAXN = 1001;public static int MAXM = 1001;// arr[i][0] i号物品的体积// arr[i][1] i号物品的价值// arr[i][2] i号物品的组号public static int[][] arr = new int[MAXN][3];public static int[] dp = new int[MAXM];public static int m, n;public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {m = (int) in.nval;in.nextToken();n = (int) in.nval;for (int i = 1; i <= n; i++) {in.nextToken();arr[i][0] = (int) in.nval;in.nextToken();arr[i][1] = (int) in.nval;in.nextToken();arr[i][2] = (int) in.nval;}Arrays.sort(arr, 1, n + 1, (a, b) -> a[2] - b[2]);out.println(compute1());}out.flush();out.close();br.close();}// 严格位置依赖的动态规划public static int compute1() {int teams = 1;for (int i = 2; i <= n; i++) {if (arr[i - 1][2] != arr[i][2]) {teams++;}}// 组的编号1~teams// dp[i][j] : 1~i是组的范围,每个组的物品挑一件,容量不超过j的情况下,最大收益int[][] dp = new int[teams + 1][m + 1];// dp[0][....] = 0for (int start = 1, end = 2, i = 1; start <= n; i++) {while (end <= n && arr[end][2] == arr[start][2]) {end++;}// start ... end-1 -> i组for (int j = 0; j <= m; j++) {// arr[start...end-1]是当前组,组号一样// 其中的每一件商品枚举一遍dp[i][j] = dp[i - 1][j];for (int k = start; k < end; k++) {// k是组内的一个商品编号if (j - arr[k][0] >= 0) {dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - arr[k][0]] + arr[k][1]);}}}// start去往下一组的第一个物品// 继续处理剩下的组start = end++;}return dp[teams][m];}// 空间压缩public static int compute2() {// dp[0][...] = 0Arrays.fill(dp, 0, m + 1, 0);for (int start = 1, end = 2; start <= n;) {while (end <= n && arr[end][2] == arr[start][2]) {end++;}// start....end-1for (int j = m; j >= 0; j--) {for (int k = start; k < end; k++) {if (j - arr[k][0] >= 0) {dp[j] = Math.max(dp[j], arr[k][1] + dp[j - arr[k][0]]);}}}start = end++;}return dp[m];}}
从栈中取出k个硬币的最大面值和
// piles是一组一组的硬币// m是容量,表示一定要进行m次操作// dp[i][j] : 1~i组上,一共拿走j个硬币的情况下,获得的最大价值// 1) 不要i组的硬币 : dp[i-1][j]// 2) i组里尝试每一种方案// 比如,i组里拿走前k个硬币的方案 : dp[i-1][j-k] + 从顶部开始前k个硬币的价值和// 枚举每一个k,选出最大值
class Solution {public static int maxValueOfCoins(List<List<Integer>> piles, int m) {int n = piles.size();int[][] dp = new int[n + 1][m + 1];for (int i = 1; i <= n; i++) {// i从1组开始(我们的设定),但是题目中的piles是从下标0开始的// 所以来到i的时候,piles.get(i-1)是当前组List<Integer> team = piles.get(i - 1);int t = Math.min(team.size(), m);// 预处理前缀和,为了加速计算int[] preSum = new int[t + 1];for (int j = 0, sum = 0; j < t; j++) {sum += team.get(j);preSum[j + 1] = sum;}// 更新动态规划表for (int j = 0; j <= m; j++) {// 当前组一个硬币也不拿的方案dp[i][j] = dp[i - 1][j];for (int k = 1; k <= Math.min(t, j); k++) {dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k] + preSum[k]);}}}return dp[n][m];}// 空间压缩public static int maxValueOfCoins2(List<List<Integer>> piles, int m) {int[] dp = new int[m + 1];for (List<Integer> team : piles) {int t = Math.min(team.size(), m);int[] preSum = new int[t + 1];for (int j = 0, sum = 0; j < t; j++) {sum += team.get(j);preSum[j + 1] = sum;}for (int j = m; j > 0; j--) {for (int k = 1; k <= Math.min(t, j); k++) {dp[j] = Math.max(dp[j], dp[j - k] + preSum[k]);}}}return dp[m];}}
把每个栈中取出硬币的情况装到一个数组中,展开讨论即可
完全背包:每种商品可以选取无限次。时间复杂度为(物品数量*背包容量)
完全背包
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;public class Code03_UnboundedKnapsack {public static int MAXM = 10001;public static int MAXT = 10000001;public static int[] cost = new int[MAXM];public static int[] val = new int[MAXM];public static long[] dp = new long[MAXT];public static int t, m;public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {t = (int) in.nval;in.nextToken();m = (int) in.nval;for (int i = 1; i <= m; i++) {in.nextToken();cost[i] = (int) in.nval;in.nextToken();val[i] = (int) in.nval;}out.println(compute2());}out.flush();out.close();br.close();}// 严格位置依赖的动态规划// 会空间不够,导致无法通过全部测试用例public static long compute1() {// dp[0][.....] = 0int[][] dp = new int[m + 1][t + 1];for (int i = 1; i <= m; i++) {for (int j = 0; j <= t; j++) {dp[i][j] = dp[i - 1][j];if (j - cost[i] >= 0) {dp[i][j] = Math.max(dp[i][j], dp[i][j - cost[i]] + val[i]);}}}return dp[m][t];}// 空间压缩// 可以通过全部测试用例public static long compute2() {Arrays.fill(dp, 1, t + 1, 0);for (int i = 1; i <= m; i++) {for (int j = cost[i]; j <= t; j++) {dp[j] = Math.max(dp[j], dp[j - cost[i]] + val[i]);}}return dp[t];}}
正则表达式匹配
public class Solution {public static boolean isMatch(String str, String pat) {char[] s = str.toCharArray();char[] p = pat.toCharArray();return f1(s, p, 0, 0);}// s[i....]能不能被p[j....]完全匹配出来// p[j]这个字符,一定不是'*'public static boolean f1(char[] s, char[] p, int i, int j) {if (i == s.length) {// s没了if (j == p.length) {// 如果p也没了,返回truereturn true;} else {// p还剩下一些后缀// 如果p[j+1]是*,那么p[j..j+1]可以消掉,然后看看p[j+2....]是不是都能消掉return j + 1 < p.length && p[j + 1] == '*' && f1(s, p, i, j + 2);}} else if (j == p.length) {// s有后缀// p没后缀了return false;} else {// s有后缀// p有后缀if (j + 1 == p.length || p[j + 1] != '*') {// s[i....]// p[j....]// 如果p[j+1]不是*,那么当前的字符必须能匹配:(s[i] == p[j] || p[j] == '?')// 同时,后续也必须匹配上:process1(s, p, i + 1, j + 1);return (s[i] == p[j] || p[j] == '.') && f1(s, p, i + 1, j + 1);} else {// 如果p[j+1]是*// 完全背包!// s[i....]// p[j....]// 选择1: 当前p[j..j+1]是x*,就是不让它搞定s[i],那么继续 : process1(s, p, i, j + 2)boolean p1 = f1(s, p, i, j + 2);// 选择2: 当前p[j..j+1]是x*,如果可以搞定s[i],那么继续 : process1(s, p, i + 1, j)// 如果可以搞定s[i] : (s[i] == p[j] || p[j] == '.')// 继续匹配 : process1(s, p, i + 1, j)boolean p2 = (s[i] == p[j] || p[j] == '.') && f1(s, p, i + 1, j);// 两个选择,有一个可以搞定就返回true,都无法搞定返回falsereturn p1 || p2;}}}// 记忆化搜索public static boolean isMatch2(String str, String pat) {char[] s = str.toCharArray();char[] p = pat.toCharArray();int n = s.length;int m = p.length;// dp[i][j] == 0,表示没算过// dp[i][j] == 1,表示算过,答案是true// dp[i][j] == 2,表示算过,答案是falseint[][] dp = new int[n + 1][m + 1];return f2(s, p, 0, 0, dp);}public static boolean f2(char[] s, char[] p, int i, int j, int[][] dp) {if (dp[i][j] != 0) {return dp[i][j] == 1;}boolean ans;if (i == s.length) {if (j == p.length) {ans = true;} else {ans = j + 1 < p.length && p[j + 1] == '*' && f2(s, p, i, j + 2, dp);}} else if (j == p.length) {ans = false;} else {if (j + 1 == p.length || p[j + 1] != '*') {ans = (s[i] == p[j] || p[j] == '.') && f2(s, p, i + 1, j + 1, dp);} else {ans = f2(s, p, i, j + 2, dp) || ((s[i] == p[j] || p[j] == '.') && f2(s, p, i + 1, j, dp));}}dp[i][j] = ans ? 1 : 2;return ans;}// 严格位置依赖的动态规划public static boolean isMatch3(String str, String pat) {char[] s = str.toCharArray();char[] p = pat.toCharArray();int n = s.length;int m = p.length;boolean[][] dp = new boolean[n + 1][m + 1];dp[n][m] = true;for (int j = m - 1; j >= 0; j--) {dp[n][j] = j + 1 < m && p[j + 1] == '*' && dp[n][j + 2];}for (int i = n - 1; i >= 0; i--) {for (int j = m - 1; j >= 0; j--) {if (j + 1 == m || p[j + 1] != '*') {dp[i][j] = (s[i] == p[j] || p[j] == '.') && dp[i + 1][j + 1];} else {dp[i][j] = dp[i][j + 2] || ((s[i] == p[j] || p[j] == '.') && dp[i + 1][j]);}}}return dp[0][0];}}
通配符匹配
public class Solution {// 暴力递归public static boolean isMatch1(String str, String pat) {char[] s = str.toCharArray();char[] p = pat.toCharArray();return f1(s, p, 0, 0);}// s[i....]能不能被p[j....]完全匹配出来public static boolean f1(char[] s, char[] p, int i, int j) {if (i == s.length) {// s没了if (j == p.length) {// 如果p也没了,返回truereturn true;} else {// 如果p[j]是*,可以消掉,然后看看p[j+1....]是不是都能消掉return p[j] == '*' && f1(s, p, i, j + 1);}} else if (j == p.length) {// s有// p没了return false;} else {if (p[j] != '*') {// s[i....]// p[j....]// 如果p[j]不是*,那么当前的字符必须能匹配:(s[i] == p[j] || p[j] == '?')// 同时,后续也必须匹配上:process1(s, p, i + 1, j + 1);return (s[i] == p[j] || p[j] == '?') && f1(s, p, i + 1, j + 1);} else {// s[i....]// p[j....]// 如果p[j]是*// 选择1: 反正当前p[j]是*,必然可以搞定s[i],那么继续 : process1(s, p, i + 1, j)// 选择2: 虽然当前p[j]是*,但就是不让它搞定s[i],那么继续 : process1(s, p, i, j + 1)// 两种选择有一个能走通,答案就是true;如果都搞不定,答案就是falsereturn f1(s, p, i + 1, j) || f1(s, p, i, j + 1);}}}// 记忆化搜索public static boolean isMatch2(String str, String pat) {char[] s = str.toCharArray();char[] p = pat.toCharArray();int n = s.length;int m = p.length;// dp[i][j] == 0,表示没算过// dp[i][j] == 1,表示算过,答案是true// dp[i][j] == 2,表示算过,答案是falseint[][] dp = new int[n + 1][m + 1];return f2(s, p, 0, 0, dp);}public static boolean f2(char[] s, char[] p, int i, int j, int[][] dp) {if (dp[i][j] != 0) {return dp[i][j] == 1;}boolean ans;if (i == s.length) {if (j == p.length) {ans = true;} else {ans = p[j] == '*' && f2(s, p, i, j + 1, dp);}} else if (j == p.length) {ans = false;} else {if (p[j] != '*') {ans = (s[i] == p[j] || p[j] == '?') && f2(s, p, i + 1, j + 1, dp);} else {ans = f2(s, p, i + 1, j, dp) || f2(s, p, i, j + 1, dp);}}dp[i][j] = ans ? 1 : 2;return ans;}// 严格位置依赖的动态规划public static boolean isMatch(String str, String pat) {char[] s = str.toCharArray();char[] p = pat.toCharArray();int n = s.length;int m = p.length;boolean[][] dp = new boolean[n + 1][m + 1];dp[n][m] = true;for (int j = m - 1; j >= 0 && p[j] == '*'; j--) {dp[n][j] = true;}for (int i = n - 1; i >= 0; i--) {for (int j = m - 1; j >= 0; j--) {if (p[j] != '*') {dp[i][j] = (s[i] == p[j] || p[j] == '?') && dp[i + 1][j + 1];} else {dp[i][j] = dp[i + 1][j] || dp[i][j + 1];}}}return dp[0][0];}}
购买干草的最小花费
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;public class Main {public static int MAXN = 101;public static int MAXM = 55001;public static int[] val = new int[MAXN];public static int[] cost = new int[MAXN];public static int[] dp = new int[MAXM];public static int n, h, maxv, m;public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {n = (int) in.nval;in.nextToken();h = (int) in.nval;maxv = 0;for (int i = 1; i <= n; i++) {in.nextToken();val[i] = (int) in.nval;maxv = Math.max(maxv, val[i]);in.nextToken();cost[i] = (int) in.nval;}// 最核心的一句// 包含重要分析m = h + maxv;out.println(compute1());}out.flush();out.close();br.close();}// dp[i][j] : 1...i里挑公司,购买严格j磅干草,需要的最少花费// 1) dp[i-1][j]// 2) dp[i][j-val[i]] + cost[i]// 两种可能性中选最小// 但是关于j需要进行一定的扩充,原因视频里讲了public static int compute1() {int[][] dp = new int[n + 1][m + 1];Arrays.fill(dp[0], 1, m + 1, Integer.MAX_VALUE);for (int i = 1; i <= n; i++) {for (int j = 0; j <= m; j++) {dp[i][j] = dp[i - 1][j];if (j - val[i] >= 0 && dp[i][j - val[i]] != Integer.MAX_VALUE) {dp[i][j] = Math.min(dp[i][j], dp[i][j - val[i]] + cost[i]);}if(j-val[i]<0&&dp[i][0]!=Integer.MAX_VALUE)dp[i][j]=Math.min(dp[i][j],dp[i][0]+cost[i]);}}int ans = Integer.MAX_VALUE;// >= h// h h+1 h+2 ... mfor (int j = h; j <= m; j++) {ans = Math.min(ans, dp[n][j]);}return dp[n][h];}// 空间压缩public static int compute2() {Arrays.fill(dp, 1, m + 1, Integer.MAX_VALUE);for (int i = 1; i <= n; i++) {for (int j = val[i]; j <= m; j++) {if (dp[j - val[i]] != Integer.MAX_VALUE) {dp[j] = Math.min(dp[j], dp[j - val[i]] + cost[i]);}}}int ans = Integer.MAX_VALUE;for (int j = h; j <= m; j++) {ans = Math.min(ans, dp[j]);}return ans;}}