挑战程序竞赛系列(2):2.3优化递推关系式
来源:互联网 发布:数显温度控制器 单片机 编辑:程序博客网 时间:2024/05/16 18:43
2.3 优化递推关系式
详细代码可以fork下Github上leetcode项目,不定期更新。
练习题如下:
1. POJ 1742: Coins
2. POJ 3046: Ant Counting
3. POJ 3181: Dollar Dayz
POJ 1742: Coins
有n种面额的硬币,面额个数分别为A_i、C_i,求最多能搭配出几种不超过m的金额?
测试用例:
3 101 2 4 2 1 12 51 4 2 10 0
该dp很有意思,用boolean[][] dp
,具有传递效果。递推式:
dp[i+1][j] |= dp[i][j-k*A[i]];
最后统计dp[n][1]~dp[n][m]
中有多少个true
即可。
代码如下:
public class SolutionDay03_P1742 {// static int MAX_N = 100;// static int MAX_M = 100000;// static boolean[][] dp = new boolean[MAX_N+1][MAX_M+1]; public static void main(String[] args) { Scanner in = new Scanner(System.in); while (true){ int n = in.nextInt(); int m = in.nextInt(); if (n == 0 && m == 0) break; int[] A = new int[n]; int[] C = new int[n]; for (int i = 0; i < n; i++){ A[i] = in.nextInt(); } for (int i = 0; i < n; i++){ C[i] = in.nextInt(); }// for (int i = 0; i < dp.length;i++){// Arrays.fill(dp[i], false);// } boolean[][] dp = new boolean[n+1][m+1]; dp[0][0] = true; for (int i = 0; i < n; i++){ for (int j = 0; j <= m; j++){ for (int k = 0; k <= C[i]; k++){ if (k * A[i] <= j){ dp[i+1][j] |= dp[i][j-k*A[i]]; } } } } int count = 0; for (int i = 1; i <= m; i++){ if (dp[n][i]) count ++; } System.out.println(count); } in.close(); }}
很遗憾,TLE,更新可视图如下:
上述情况不算if
条件的过滤,时间复杂度为
如当i = 0, j = 3
时,它也会有三次伪更新,但这三次更新是否有必要?显然完全不需要,因为我们知道dp[0][3] = 0
。所以,程序很明显会TLE。
这里的主要原因在于,我们在计算下一阶段状态时,丢失了一些非常关键的信息。试想一下,我们能否在下一阶段带上前一阶段的硬币数?
观察上述更新过程,你会发现,它的更新很有特点,如从阶段0 -> 阶段1
,有三次有效更新,而三次正是硬币可选择的个数0,1,2
。同理,阶段1 -> 阶段2
,两次有效更新,而硬币选择的个数为0,1
。所以,给了我们一个启发,把硬币个数带入下一阶段,然后让它平行更新。
所以有了新的递推式:
dp[i][j] = c[i] // 表示当前能够更新的最大可能数。dp[i][j] = -1 // 表示当前无法再更新更新规则:// 上一阶段跳入下一阶段dp[i+1][j] = C[i], if dp[i][j] >= 0 // 说明当前面值可以在下一阶段递增// 越界(当前面值超过最大数m)或者当前阶段没有硬币可用dp[i+1][j] = -1, if j < A[i] || dp[i+1][j-A[i]] <= 0// 用掉一个硬币,递推式减1,直到-1dp[i+1][j] = dp[i+1][j-A[i]] - 1;
代码如下:
public class SolutionDay03_P1742 {// static int MAX_N = 100;// static int MAX_M = 100000;// static boolean[][] dp = new boolean[MAX_N+1][MAX_M+1]; public static void main(String[] args) { Scanner in = new Scanner(System.in); while (true){ int n = in.nextInt(); int m = in.nextInt(); if (n == 0 && m == 0) break; int[] A = new int[n]; int[] C = new int[n]; for (int i = 0; i < n; i++){ A[i] = in.nextInt(); } for (int i = 0; i < n; i++){ C[i] = in.nextInt(); } System.out.println(solve(n, m, A, C)); } in.close(); } public static int solve(int n, int m, int[] A, int[] C){ int[][] dp = new int[n+1][m+1]; for (int i = 0; i < dp.length; i++){ Arrays.fill(dp[i], -1); } dp[0][0] = 0; for (int i = 0; i < n; i++){ for (int j = 0; j <= m; j++){ if (dp[i][j] >= 0){ dp[i+1][j] = C[i]; } else if (j < A[i] || dp[i+1][j - A[i]] <= 0){ dp[i+1][j] = -1; } else{ dp[i+1][j] = dp[i+1][j-A[i]] - 1; } } } int answer = 0; for (int i = 1; i <= m; i++){ if (dp[n][i] >= 0) answer++; } return answer; }}
我们再来看看它的更新图,如下:
呵呵,MLE了,真是题目虐我千百遍,我待她如初恋啊,书上也说了,有些题就得重复利用数组。为啥可以?看上图,平行更新,且由if
判断的顺序决定。代码如下:
public static int solve(int n, int m, int[] A, int[] C){ int[] dp = new int[m+1]; Arrays.fill(dp, -1); dp[0] = 0; for (int i = 0; i < n; i++){ for (int j = 0; j <= m; j++){ if (dp[j] >= 0){ dp[j] = C[i]; } else if (j < A[i] || dp[j-A[i]] <= 0){ dp[j] = -1; } else{ dp[j] = dp[j-A[i]] - 1; } } } int answer = 0; for (int i = 1; i <= m; i++){ if (dp[i] >= 0) answer++; } return answer; }
POJ 3046: Ant Counting
蚂蚁牙黑,蚂蚁牙红:有A只蚂蚁,来自T个家族。同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同。任取n只蚂蚁(S<=n<=B),求能组成几种集合?
一道排列组合题,问题可以归简为:从元素为n个的集合中,取出m个元素,总共有多少种取法?
dp[i][j] 表示前i个家族中,取出j个元素的组合总数初始化:dp[0][0] = 1, 表示无蚂蚁,取出0个元素。(空集)
递推式:
按书中的定义得,从前
两点:
dp[i−1][j−k] 的值代表了取j−k 个元素的组合数,每个组合都是唯一代表一个集合。- 当有
k 个相同的元素加入到新的集合中,就有了dp[i][j]=1×dp[i−1][j−k] ,因为第i 个家庭的k 个元素组合始终为1,该家族的每个元素无差别。
代码如下:
public class SolutionDay03_P3046 { static final int MOD = 1000000; static int[] family = new int[1000+16]; public static void main(String[] args) { Scanner in = new Scanner(System.in); int T = in.nextInt(); int A = in.nextInt(); int S = in.nextInt(); int B = in.nextInt(); for (int i = 0; i < A; i++){ int index = in.nextInt(); ++family[index]; } System.out.println(solve(T,A,S,B,family)); in.close(); } public static int solve(int T, int A, int S, int B, int[] family){ int[][] dp = new int[T+1][10000 + 16]; dp[0][0] = 1; int total = family[0]; for (int i = 1; i <= T; ++i){ total += family[i]; for (int k = 0; k <= family[i]; ++k){ for (int j = total; j >= k; --j){ dp[i][j] = (dp[i][j] + dp[i-1][j-k]) % MOD; } } } int ans = 0; for (int i = S; i <= B; ++i){ ans = (ans + dp[T][i]) % MOD; } return ans; }}
上述时间和空间都不尽人如意,递推式优化:
代码如下:
public static int solve(int T, int A, int S, int B, int[] family){ int[][] dp = new int[2][A+1]; dp[0][0] = dp[1][0] = 1; int total = A; for (int i = 1; i <= T; ++i){ for (int j = 1; j <= total; ++j){ if (j - 1 - family[i] >= 0){ dp[i%2][j] = (dp[i%2][j-1] - dp[(i-1)%2][j-1-family[i]] + dp[(i-1)%2][j] + MOD) % MOD; }else{ dp[i%2][j] = (dp[i%2][j-1] + dp[(i-1)%2][j] ) % MOD; } } } int ans = 0; for (int i = S; i <= B; ++i){ ans = (ans + dp[T%2][i]) % MOD; } return ans; }
POJ 3181: Dollar Dayz
农夫约翰有N元钱,市场上有价值1……K的商品无限个,求所有的花钱方案?
dp[i][j] :用i种价格配出金额j的方案数初始化:dp[i][0] = 1; 用i中价格配出金额0的方案数为1递推式:dp[i][j] = dp[i-1][j] + dp[i-1][j-i] + dp[i-1][j-2*i] + ... + dp[i-1][0]如:1 * x + 2 * y = 5;y = 0, 1 * x = 5 y = 1, 1 * x = 3y = 2, 1 * x = 1dp[2][5] = dp[1][5] + dp[1][3] + dp[1][1]
代码如下:
public class SolutionDay04_P3181 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int N = in.nextInt(); int K = in.nextInt(); System.out.println(solve(N, K)); in.close(); } public static BigInteger solve(int N, int K){ BigInteger[][] dp = new BigInteger[K + 1][N + 1]; for (int i = 0; i < dp.length; i++){ for (int j = 0; j < dp[0].length; j++){ dp[i][j] = BigInteger.ZERO; } } dp[0][0] = BigInteger.ONE; for (int i = 1; i <= K; ++i) { for (int k = 0; k <= N; k += i) { for (int j = N; j >= k; --j) { dp[i][j] = dp[i][j].add(dp[i - 1][j - k]); } } } return dp[K][N]; }}
递推式优化:
dp[i][j] = dp[i-1][j] + dp[i-1][j-i] + dp[i-1][j-2*i] + ... + dp[i-1][0];dp[i][j-i] = dp[i-1][j-i] + dp[i-1][j-2*i] + dp[i-1][j-3*i] + ... + dp[i-1][0];所以有:dp[i][j] = dp[i-1][j] + dp[i][j-i];
代码如下:
public static BigInteger solve(int N, int K){ BigInteger[][] dp = new BigInteger[K + 1][N + 1]; for (int i = 0; i < dp.length; i++){ for (int j = 0; j < dp[0].length; j++){ dp[i][j] = BigInteger.ZERO; } } dp[0][0] = BigInteger.ONE; for (int i = 1; i <= K; ++i) { //必须得初始化,dp[i][0]不能由dp[0][0]推得 dp[i][0] = BigInteger.ONE; for (int j = 1; j <= N; ++j){ if (j < i){ dp[i][j] = dp[i-1][j]; } else{ dp[i][j] = dp[i-1][j].add(dp[i][j-i]); } } } return dp[K][N]; }
- 挑战程序竞赛系列(2):2.3优化递推关系式
- 3046(dp优化递推关系式)
- 《挑战程序设计竞赛》2.3.2 动态规划-优化递推 POJ1742 3046 3181
- poj1742(dp优化递推关系式)
- poj3181(dp优化递推关系式)
- 挑战程序竞赛系列(1):2.3动态规划
- 挑战程序竞赛系列(82):4.3 LCA(2)
- POJ 3046 Ant Counting(“动态规划” 优化递推关系式)
- 挑战程序竞赛系列(81):4.3 LCA(1)
- 挑战程序竞赛系列(4):2.1深度优先搜索
- 挑战程序竞赛系列(5):2.1广度优先搜索
- 挑战程序竞赛系列(6):2.1穷尽搜索
- 挑战程序竞赛系列(9):2.4优先队列
- 挑战程序竞赛系列(10):2.4并查集
- 挑战程序竞赛系列(11):2.5最短路径
- 挑战程序竞赛系列(12):2.5最小生成树
- 挑战程序竞赛系列(13):2.6辗转相除法
- 挑战程序竞赛系列(14):2.6素数
- [python爬虫] Selenium常见元素定位方法和操作的学习介绍
- angularjs2 for Mac环境安装
- kafka参数说明
- windows anaconda下安装xgboost
- Android studio 使用注解插件 ButterKnife 的正确方法
- 挑战程序竞赛系列(2):2.3优化递推关系式
- layui select change事件
- 使用sklearn中svm做多分类时难点解惑
- opencv -dnn人脸识别(2016-10-27)
- OpenGL ES2.0 的三种变量类型(uniform,attribute和varying)
- ESP8266
- tinny阅读
- react native DatePickerIOS 使用详解
- Struts第八篇【资源国际化、对比JSP的资源国际化】