背包问题
背包问题的本质:给我们一堆物品,每个物品有体积v有价值w,有各种限制,背包装的下地前提下,最多能装多少(背包不一定装满)
01背包问题:每件物品最多只能用一次
完全背包:每件物品可以用无限次
多重背包:每件物品的数量不一样
分组背包:物品有n组,每一组有若干个
背包问题的思路
dp问题,一般就是两步,状态表示和状态计算
状态表示 f(i,j)
表示集合的某种属性
集合:所有选法(条件限制:只从前i个物品中选,总体积 <= j)
属性:常见的有max, min, 数量(属性是一个值,集合里每种选法存储的值)
状态计算其实就是集合的划分,当前的集合分为哪些类,然后根据属性得到f(i,j)
的状态转移方程
dp的优化和分析是分开的,我们一般先把朴素做法分析完后,再优化 优化其实就是对常规dp的代码或者方程做一个等价的变形 dp是一种思想,不存在固定的模板
基本思路 对于N件物品,每件物品都有选or不选两种情况,因此总共有2^N种情况,我们将这看作一个集合,然后找到集合里最大值的情况。
如果用DFS来做的话,当然也是可以的,但是复杂度显然是指数级别的。
有限集合里的最优解问题,可以考虑用DP来做
所以DP问题两步走
状态表示:f(i,j)
)的定义是什么? 两个方面,首先对应的集合 是什么:从前i个物品里选,且总体积不超过j的选法集合 然后就是我们所关注的是集合里的什么属性 (集合里每种选法存的是什么值):这里关注的是最大值,也就是集合里存储的是每种方案的最大值。 所以f(i,j)的定义就是从前i个物品里选,且总体积不超过j的选法集合中的最大值
状态计算 把集合进行划分,得到状态转移方程,怎么划分(这里是找到最后一个物品选还是不选) 分成两部分,一部分是不选第i物品的方案,另一部分是包含第i物品的方案,然后两部分取max,所以状态转移方程f(i, j) = max(f(i - 1, j) + f(i - 1, j - v(i)) + w(i))
滚动数组优化
优化这里我们发现i只依赖于i-1,所以可以通过滚动数组优化空间,常见的方式有取模或者&
二维优化到一维 首先我们发现i只和i - 1有关,所以可以去掉i,优化成一维。然后对于j,我们是小(j -vi) 更新大(j) ,所以如果是正序,大的更新的时候,小的已经先被更新,那么两边都是第i轮; 如果是倒序,大的更新的时候,小的还没有被更新,大的是第i轮,小的是第i - 1轮;这里我们需要的是i - 1,所以我们需要倒序。
优化口诀 小的更新大的,若用到上一层,从大到小遍历,用到当前层,从小到大遍历
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int v[N]; int w[N]; int f[N][N]; int main () { int n,m; scanf ("%d%d" ,&n,&m); for (int i = 1 ;i <= n;i ++) scanf ("%d%d" ,&v[i],&w[i]); for (int i = 1 ;i <= n;i ++){ for (int j = 0 ;j <= m;j ++){ if (j < v[i]) f[i][j] = f[i - 1 ][j]; else f[i][j] = max (f[i - 1 ][j],f[i - 1 ][j - v[i]] + w[i]); } } printf ("%d" ,f[n][m]); return 0 ; }#include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int v[N]; int w[N]; int f[N][N]; int main () { int n,m; scanf ("%d%d" ,&n,&m); for (int i = 1 ;i <= n;i ++) scanf ("%d%d" ,&v[i],&w[i]); for (int i = 1 ;i <= n;i ++){ for (int j = 0 ;j <= m;j ++){ f[i][j] = f[i - 1 ][j]; if (j >= v[i]) f[i][j] = max (f[i][j],f[i - 1 ][j - v[i]] + w[i]); } } printf ("%d" ,f[n][m]); return 0 ; }#include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int v[N];int w[N];int f[2 ][N];int main () { int n,m; scanf ("%d%d" ,&n,&m); for (int i = 1 ;i <= n;i ++) scanf ("%d%d" ,&v[i],&w[i]); for (int i = 1 ;i <= n;i ++){ for (int j = 0 ;j <= m;j ++){ f[i & 1 ][j] = f[i - 1 & 1 ][j]; if (j >= v[i]) f[i & 1 ][j] = max (f[i & 1 ][j],f[i - 1 & 1 ][j - v[i]] + w[i]); } } printf ("%d" ,f[n & 1 ][m]); return 0 ; }#include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int n, m;int v[N], w[N];int f[N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i ++ ) cin >> v[i] >> w[i]; for (int i = 1 ; i <= n; i ++ ) for (int j = m; j >= v[i]; j -- ) f[j] = max (f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return 0 ; }
Reference [1]. yxc [2]. 01背包问题代码优化完全详解 [3]. 01背包问题 简要思路) [4]. 二维到一维详解 [5]. 以01背包为例, 归纳总结算法导论中动态规划的解题步骤并深度探索原理和推广
基本思路 01背包问题的变形,还是用动态规划来做
动态规划两步走
状态表示f(i,j), 它表示的是前i个物品,总体积不大于j的所有选法集合中的最大值
状态计算 这里集合划分,我们之前01背包问题是根据最后一个物品选or不选分成两部分 这里我们第i个物品可以选无限多个,所以可以划分成0~k个子集(直到不能选为止) f[i][j] = max(f[i-1][j] + f[i-1][j- v] + w + f[i-1][j-2v] + 2w + …])
优化 先优化时间复杂度,最后一层枚举k,总共三层for循环,时间复杂度很高,所以这里我们要对状态方程进行化简 最后发现通过
1 2 3 f[i][j] = max (f[i-1 ][j], f[i-1 ][j-v]+w, f[i-1 ][j-2 v]+2 w ...) f[i][j-v] = max (f[i-1 ][j-v] + f[i-1 ][j-2 v] + w ...) 简化方程为 f[i][j] = max (f[i-1 ][j], f[i][j-v] + w)
然后优化空间复杂度,因为i只依赖于i-1,所以可以滚动数组优化,然后j只依赖比它小的值,所以可以优化成一维数组
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int n,m;int v[N],w[N];int f[N][N];int main () { cin>>n>>m; for (int i = 1 ;i <= n;i ++) cin>>v[i]>>w[i]; for (int i = 1 ;i <= n;i ++){ for (int j = 0 ;j <= m;j ++){ for (int k = 0 ;k <= j / v[i];k ++){ f[i][j] = max (f[i][j],f[i - 1 ][j - v[i] * k] + w[i] * k); } } } cout<<f[n][m]<<endl; return 0 ; }#include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int n,m;int v[N],w[N];int f[2 ][N];int main () { cin>>n>>m; for (int i = 1 ;i <= n;i ++) cin>>v[i]>>w[i]; for (int i = 1 ;i <= n;i ++){ for (int j = 0 ;j <= m;j ++){ for (int k = 0 ;k <= j / v[i];k ++){ f[i & 1 ][j] = max (f[i & 1 ][j],f[i - 1 & 1 ][j - v[i] * k] + w[i] * k); } } } cout<<f[n & 1 ][m]<<endl; return 0 ; }#include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int n,m;int v[N],w[N];int f[N][N];int main () { cin>>n>>m; for (int i = 1 ;i <= n;i ++) cin>>v[i]>>w[i]; for (int i = 1 ;i <= n;i ++){ for (int j = 0 ;j <= m;j ++){ f[i][j] = f[i - 1 ][j]; if (j >= v[i]) f[i][j] = max (f[i][j],f[i][j - v[i]] + w[i]); } } cout<<f[n][m]<<endl; return 0 ; }#include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int n, m;int v[N], w[N];int f[N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i ++ ) cin >> v[i] >> w[i]; for (int i = 1 ; i <= n; i ++ ) for (int j = v[i]; j <= m; j ++ ) f[j] = max (f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return 0 ; }
Reference [1]. yxc [2]. 完全背包问题(良心正解)
基本思路 和完全背包的朴素做法几乎一样,唯一区别就是完全背包每件物品可以用无限次,而多重背包每件物品只能用有限次。
注意:
状态计算时,k属于[0, s[i]],所有子集并不一定合法,所以需要限制条件,使其合法。 条件就是 k * v[i] <= j
这里因为数据量小,所以可以用三重循环的朴素做法,数据量大的时候,会TLE
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <algorithm> using namespace std;const int N = 110 ;int n, m;int v[N], w[N], s[N];int f[N][N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i ++ ) cin >> v[i] >> w[i] >> s[i]; for (int i = 1 ; i <= n; i ++ ) for (int j = 0 ; j <= m; j ++ ) for (int k = 0 ; k <= s[i] && k * v[i] <= j; k ++ ) f[i][j] = max (f[i][j], f[i - 1 ][j - v[i] * k] + w[i] * k); cout << f[n][m] << endl; return 0 ; }
Reference [1]. yxc [2]. 多重背包问题I(良心正解)
基本思路 多重背包2这里增大了数据范围,所以朴素做法会超时,所以我们尝试优化状态转移方程,发现由于si个数的限制,无法像完全背包问题一样优化状态转移方程 所以这里我们采取另一种优化思路:二进制拆分 朴素做法里需要枚举k个数,这里我们将k进行二进制拆分 拆成logk(上取整)个数,每个数分别是$2^0, 2^1, 2^(logk - 1)$. 然后任意的k我们都可以用这些数通过选or不选得到 所以就转化成了01背包问题
注意:
完全背包问题和多重背包问题其实有两种理解:这两种模型其实是等价的
i件物品,每件物品可以用无限次(完全背包)或者s次(多重背包)
i种物品,每种物品有无数个(完全背包)或者s个(多重背包)
我们之前都是基于第一种理解,总共n件物品,所以v,w数组大小是n,然后直接每件物品读入w,n数组; 但是如果涉及到拆分的话,其实就是第二种理解,物品总数大约是n * s种(为什么是大约,因为不同种类物品,个数不同)所以v,w数组大小是n * s
,然后我们在每种物品里相当于把s件拆成了log s
件,所以物品件数,v,w数组大小缩小至n*logs
转化为01背包问题后,需要优化到一维,否则会数组过大导致内存溢出MLE
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> using namespace std;const int N = 11 * 1000 + 10 , M = 2010 ;int n, m;int v[N], w[N];int f[M];int main () { cin >> n >> m; int cnt = 0 ; for (int i = 1 ; i <= n; i ++) { int a, b, s; cin >> a >> b >> s; int k = 1 ; while (k <= s) { cnt ++; v[cnt] = a * k; w[cnt] = b * k; s -= k; k *= 2 ; } if (s > 0 ) { cnt ++; v[cnt] = a * s; w[cnt] = b * s; } } n = cnt; for (int i = 1 ; i <= n; i ++) for (int j = m; j >= v[i]; j --) f[j] = max (f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return 0 ; }
Reference [1]. yxc [2]. 优化方法详解
基本思路 与完全背包问题的唯一区别,就是完全背包是第i个物品选几个,而分组背包是第i个物品选哪个。
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 版本1 朴素版#include <iostream> #include <algorithm> using namespace std;const int N = 110 ;int f[N][N];int v[N][N], w[N][N], s[N];int n, m;int main () { cin >> n >> m; for (int i = 1 ; i <= n; i ++){ cin >> s[i]; for (int j = 1 ; j <= s[i]; j ++){ cin >> v[i][j] >> w[i][j]; } } for (int i = 1 ; i <= n; i ++){ for (int j = 1 ; j <= m; j ++){ f[i][j] = f[i - 1 ][j]; for (int k = 1 ; k <= s[i]; k ++){ if (j >= v[i][k]){ f[i][j] = max (f[i][j], f[i - 1 ][j - v[i][k]] + w[i][k]); } } } } cout << f[n][m] << endl; return 0 ; } 版本2 优化一维数组#include <iostream> #include <algorithm> using namespace std;const int N = 110 ;int n, m;int v[N][N], w[N][N], s[N];int f[N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; i ++ ) { cin >> s[i]; for (int j = 0 ; j < s[i]; j ++ ) cin >> v[i][j] >> w[i][j]; } for (int i = 1 ; i <= n; i ++ ) for (int j = m; j >= 0 ; j -- ) for (int k = 0 ; k < s[i]; k ++ ) if (v[i][k] <= j) f[j] = max (f[j], f[j - v[i][k]] + w[i][k]); cout << f[m] << endl; return 0 ; }
Reference [1]. AcWing 9. 分组背包问题 [2]. AcWing 9. 分组背包问题 [3]. AcWing 9. 分组背包问题(良心正解)
数字三角形问题
基本思路 这道题目首先考虑有多少种选法,$2^{n-1}$种选法,然后选出最优解,如果用DFS爆搜的话,结合数据范围必然会超时,所以我们采用DP的方法。
DP问题两步走
状态表示:i表示行,j表示列(这里是三角形,所以j可以理解为该行的第几个 = 列) 这里可以从上到下看,也可以从下到上看,这里我们先讨论从上到下看 f[i][j]的定义就是从(1,1)到(i,j)的所有路径的最大值
状态计算 这里我们进行集合的划分,可以发现(i,j)只能从左上方or右上方走到该点 所以集合可以划分为两部分,然后得到状态转移方程f[i][j]=max(f[i-1][j-1],f[i-1][j])
最后枚举最后一层元素,取f的最大值即可
这里要注意 三角形两边的元素没有左上/右上元素,需要把没有元素的地方数字置为负无穷
或者我们可以从下往上看
状态表示 f[i][j]的定义就是从(i,j)到(1,1)的所有路径的最大值
这样的话不用考虑边界问题,也不用枚举最后一层才可以得到答案,代码会简洁一些
状态计算 也是集合划分为两部分,从左下角or右下角走到该点(i,j) 所以状态转移方程f[i][j]=max(f[i+1][j],f[i+1][j+1])
,最后我们求解的就是f(1,1)
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 版本1 从上到下#include <iostream> using namespace std;const int N = 510 , INF = 1e9 ;int n;int a[N][N]; int f[N][N]; int main () { cin>>n; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=i;j++) cin>>a[i][j]; for (int i=1 ;i<=n;i++) for (int j=0 ;j<=i+1 ;j++) f[i][j] = -INF; f[1 ][1 ] = a[1 ][1 ]; for (int i=2 ;i<=n;i++) for (int j=1 ;j<=i;j++) f[i][j] = max (f[i-1 ][j],f[i-1 ][j-1 ]) + a[i][j]; int res = -INF; for (int i=1 ;i<=n;i++) res = max (res,f[n][i]); cout<<res<<endl; return 0 ; } 版本2 从下到上#include <iostream> using namespace std;const int N = 510 , INF = 1e9 ;int n;int a[N][N]; int f[N][N]; int main () { cin>>n; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=i;j++) cin>>a[i][j]; for (int i=1 ;i<=n;i++) f[n][i] = a[n][i]; for (int i=n;i>=1 ;i--) for (int j=i;j>=1 ;j--) f[i][j] = max (f[i+1 ][j],f[i+1 ][j+1 ]) + a[i][j]; cout<<f[1 ][1 ]<<endl; return 0 ; }
Reference [1]. AcWing 898. 数字三角形 [2]. AcWing 898. 数字三角形 [3]. AcWing 898. 数字三角形的正序解法, 倒序解法, 正序的空间复杂度优化(二维转一维滚动数组)
tips: 如何计算动态规划的时间复杂度,就是用状态数 * 状态计算的时间复杂度,这里状态数是O(N^2),计算是O(1)。然后一个题目的数据范围非常重要,我们可以通过数据范围推测大概能用的算法
最长上升子序列问题
最优问题,动态规划,DFS,因为数据范围是1000,所以显然用DFS大概率超时,所以选择动态规划
基本思路 动态规划两步走
状态表示,这里可以不需要二维了,直接一维f(i)定义为以i结尾的所有上升子序列的最大值
状态计算,怎么划分集合呢,一般观察最后一步,最后一步就是倒数第二个数,所以可以根据倒数第二个数划分(枚举倒数第二个数),如果合法就得到状态转移方程f[i] = max(f[i], f[j] + 1)
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <algorithm> using namespace std;const int N = 1010 ;int a[N]; int f[N]; int main () { int n; scanf ("%d" ,&n); for (int i = 1 ;i <= n;i ++) scanf ("%d" ,&a[i]); for (int i = 1 ;i <= n;i ++){ f[i] = 1 ; for (int j = 1 ;j <= i;j ++){ if (a[i] > a[j]) f[i] = max (f[i],f[j] + 1 ); } } int res = -0x3f3f3f3f ; for (int i = 1 ;i <= n;i ++) res = max (res,f[i]); printf ("%d\n" ,res); return 0 ; }
Reference [1]. AcWing 895. 最长上升子序列 [2]. AcWing 895. 最长上升子序列
基本思路 dp两步走
状态表示 f(i,j)定义为所有a[1..i]和b[1…j]的公共子序列集合的最大值
状态计算 这里还是按照最后一步去集合划分,是否包含i和j,分成四种情况,00,01,10,11。其中要注意01,10,他不是简单的等价于f(i-1, j)和f(i, j -1),因为f(i-1, j)结果里j不一定出现,但是01的话j一定出现,所以f(i-1, j)实际上是包含了01这种情况,10也是同理,由于我们求得是max而不是数量,所以只需要做到不漏,不需要做到不重复,得到状态转移方程:f(i,j) = max(f[i - 1][j] , f[i][j - 1]), if(a[i] == b[j]) f[i][j] = max(f[i][j] , f[i - 1][j - 1] + 1)
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;const int N = 1010 ;int n , m;char a[N] , b[N];int f[N][N];int main () { cin >> n >> m; cin >> a + 1 >> b + 1 ; for (int i = 1 ; i <= n ; i++) for (int j = 1 ; j <= m ; j++) { f[i][j] = max (f[i - 1 ][j] , f[i][j - 1 ]); if (a[i] == b[j]) f[i][j] = max (f[i][j] , f[i - 1 ][j - 1 ] + 1 ); } cout << f[n][m] << endl; return 0 ; }
Referenece [1]. AcWing 897. 最长公共子序列 [2]. AcWing 897. 最长公共子序列 [3]. AcWing 897. 最长公共子序列
区间DP问题
基本思路 DP问题两步走
状态表示 f(i,j)定义的是所有将[i,j]合成一堆的方案的集合的最小值
状态计算 如何划分集合,这里还是看最后一步,最后一步是两个堆合并,左边的堆可以是[i, k], k属于[i, j - 1],所以根据这个进行集合的划分,得到状态转移方程:f[l][r] = min(f[l][r],f[l][k] + f[k + 1][r] + s[r] - s[l - 1])
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <algorithm> using namespace std;const int N = 310 ;int n;int s[N]; int f[N][N];int main () { cin>>n; for (int i = 1 ;i <= n;i ++){ cin>>s[i]; s[i] += s[i - 1 ]; } for (int len = 2 ;len <= n;len ++){ for (int i = 1 ;i + len - 1 <= n;i ++){ int l = i,r = i + len - 1 ; f[l][r] = 0x3f3f3f3f ; for (int k = l;k < r;k ++){ f[l][r] = min (f[l][r],f[l][k] + f[k + 1 ][r] + s[r] - s[l - 1 ]); } } } cout<<f[1 ][n]<<endl; return 0 ; }
Reference [1]. AcWing 282. 石子合并 [2]. AcWing 282. 石子合并 [3]. AcWing 282. 石子合并
数位DP问题 y总翻车了…,之后提高课总综合一起写
状态压缩DP问题
基本思路 太复杂了,我自己是不太好总结了,多看看别人的消化一下把…[1]. ShizhengLee [2].蒙德里安的梦想(大佬们写的都挺完整的,做一个补充) [3]. AcWing 291. 本题关键点总结 [4]. 蒙德里安的梦想(详细注释 )
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iostream> #include <cstring> using namespace std;const int N = 12 ;const int M = 1 << N;long long f[N][M];bool st[M];int n, m;int main () { while (cin >> n >> m, n || m) { for (int i = 0 ; i < 1 << n; i++) { st[i] = true ; int cnt = 0 ; for (int j = 0 ; j < n; j++) { if (i >> j & 1 ) { if (cnt & 1 ) { st[i] = false ; break ; } }else cnt++; } if (cnt & 1 ) st[i] = false ; } memset (f, 0 , sizeof f); f[0 ][0 ] = 1 ; for (int i = 1 ; i <= m; i++) { for (int j = 0 ; j < 1 << n; j++) { for (int k = 0 ; k < 1 << n; k++) { if ((j & k) == 0 && st[j | k]) { f[i][j] += f[i - 1 ][k]; } } } } cout << f[m][0 ] << endl; } return 0 ; }
基本思路
状态压缩dp问题,就不写自己的了hh[1]. yxc [2]. 冰中月 [3]. 最短Hamilton路径(管家级详解) [4]. 垫底抽风 [5]. Bug-Free
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 20 ,M = 1 << N;int n;int w[N][N]; int f[M][N]; int main () { scanf ("%d" ,&n); for (int i = 0 ;i < n;i ++){ for (int j = 0 ;j < n;j ++){ scanf ("%d" ,&w[i][j]); } } memset (f,0x3f ,sizeof f); f[1 ][0 ] = 0 ; for (int i = 0 ;i < 1 << n;i ++){ for (int j = 0 ;j < n;j ++){ if (i >> j & 1 ){ for (int k = 0 ;k < n;k ++){ if ((i - (1 << j)) >> k & 1 ){ f[i][j] = min (f[i][j],f[i - (1 << j)][k] + w[k][j]); } } } } } printf ("%d\n" ,f[(1 << n) - 1 ][n - 1 ]); return 0 ; }
树形DP问题 基本思路 [1]. 没有上司的舞会(良心正解
参考代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 6010 ;int n;int happy[N]; int f[N][2 ]; int e[N],ne[N],h[N],idx; bool has_father[N]; void add (int a,int b) { e[idx] = b,ne[idx] = h[a],h[a] = idx ++; }void dfs (int u) { f[u][1 ] = happy[u]; for (int i = h[u];~i;i = ne[i]){ int j = e[i]; dfs (j); f[u][0 ] += max (f[j][1 ],f[j][0 ]); f[u][1 ] += f[j][0 ]; } }int main () { scanf ("%d" ,&n); for (int i = 1 ;i <= n;i ++) scanf ("%d" ,&happy[i]); memset (h,-1 ,sizeof h); for (int i = 1 ;i < n;i ++){ int a,b; scanf ("%d%d" ,&a,&b); has_father[a] = true ; add (b,a); } int root = 1 ; while (has_father[root]) root ++; dfs (root); printf ("%d\n" ,max (f[root][0 ],f[root][1 ])); return 0 ; }
记忆化搜索DP
基本思路 [1]. 滑雪(良心正解) [2]. 滑雪—闫式dp分析法分析:一种更容易理解的思路+图解 [3]. Bug-Free
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 310 ;int n,m; int f[N][N]; int h[N][N]; int dx[4 ] = {-1 ,0 ,1 ,0 };int dy[4 ] = {0 ,1 ,0 ,-1 };int dp (int x,int y) { int &v = f[x][y]; if (v != -1 ) return v; v = 1 ; for (int i = 0 ;i < 4 ;i ++){ int xx = x + dx[i]; int yy = y + dy[i]; if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && h[x][y] > h[xx][yy]){ v = max (v,dp (xx,yy) + 1 ); } } return v; }int main () { cin>>n>>m; for (int i = 1 ;i <= n;i ++){ for (int j = 1 ;j <= m;j ++){ cin>>h[i][j]; } } memset (f,-1 ,sizeof f); int res = 0 ; for (int i = 1 ;i <= n;i ++){ for (int j = 1 ;j <= m;j ++){ res = max (res,dp (i,j)); } } cout<<res<<endl; return 0 ; }