代码(工具)
类型1--普通
// 从上向下--以菲列波数列为例子
int memoize[N];
int fib(int n) {
if (n == 1 || n == 2) return 1; // 底部直接返回
if (memoize[n]) return memoize[n]; // 如果存在--已经搜索过就返回
memoize[n] = fib(n - 1) + fib(n - 2); // 向下面调用
return memoize[n];
}
// 从下向上--以菲列波数列为例子
int dp[N];
int fib(int n) {
dp[1] = dp[2] = 1; // 初始化DP数组
for (int i = 3; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2]; // DP公式
return dp[n];
}
类型2(背包)
背包问题多是二维的,所以心里面要有一个二维的图
注意:
1、从cost【i】到v 是有无数件的 从v到cost【i】是有限个物品的
2、排序(性价比最高的在前面)--如果可以优化
3、初始化为0 或 负无穷 ,需要根据题目判断---结果是否应该装满 装满就负无穷,否则就0
(1)一个物品有k个(可选取)
// 遍历每一层
for (int i = 1; i <= n; i++) { // p是这种物品的数量
// 背包最大可以装多少个这种物品
int num = min(p[i], V / c[i]); // V/c[i] 是最多能放多少个进去,优化上界
// 取k个进行尝试
for (int k = 1; k <= num; k <<= 1) {
// 从大到小
for (int j = V; j >= c[i] * k; j--) // 01背包
f[j] = max(f[j], f[j - c[i] * k] + w[i] * k); // 要么上一层不表,要么进行装包
}
}
(2)一个物品有无限个可以进行选取
for(int i=1;i<=n;i++){ // 遍历每一层
for(int j=1;j<=v;j++) // 从小到大就是无限型的选取模式
f[j] = max( f[j] ,f[j-cost[i]]+worth[i] );
}
(3)一个物品只能选择一个
for(int i=1;i<=n;i++){ // 枚举每一个物品(遍历每一层)
for(int j = v; j >= cost[i]; j--) // 从大到小就是只能选择一个的模板
f[j] = max(f[j], f[j - cost[i]] + worth[i]); //要么装,要么不装
}
(4)组合背包(前面三种的组合)
for (int i = 1; i <= n; i++) {
cin >> c >> w >> p; // 这个物品的成本,价值, 采取上面的什么模式
if (p == 0) // 多个物品
for (int j = c; j <= V; j++)
f[j] = max(f[j], f[j - c] + w);
else if (p == -1) // 一种物品只能拿一个
for (int j = V; j >= c; j--)
f[j] = max(f[j], f[j - c] + w);
else { // 多重背包二进制优化-------这里采用了优化(上面的是没有优化的)
// 获取最大数量
int num = min(p, V / c); //最多可以装多少个
// 开始装包
for (int k = 1; num > 0; k <<= 1) { // 每次都乘2,直到不能乘了
if (k > num) k = num; // 不能乘了
num -= k;
for (int j = V; j >= c * k; j--)
f[j] = max(f[j], f[j - c * k] + w * k);
}//不是一个一个装,而是1个,2个,4个,8个,……效率高
}
}
/* 解释上面的二进制部分
假设 num = 5 , 所以 k 刚开始是1,k 小于 num ,所以 num = 5 - 1 = 4,先装1(k)个物品
然后 k = 1 * 2 = 2; k 小于 num ,所以 num = 4 - 2 = 2, 再装2(k)个物品
然后 k = 2 * 2 = 4; k 大于 num , 所以 k = num = 2; 只能拿剩余的2(k)个进行装包了
总结:相比于 1 1 1 1 1 的方式 装包, 1 2 2 的方式更加高效,如果还不懂, 就使用上面的那个
*/
(5)二维费用背包
#二维费用背包
for (int i = 1; i <= n; i++)
for (int j = V; j >= c[i]; j--) //一维费用
for (int k = M; k >= g[i]; k--) //二维费用
f[j][k] = max(f[j][k], f[j - c[i]][k - g[i]] + w[i]);
(6)分组背包
// 循环每一个组
for (int i = 1; i <= n; i++) {
// 第i组的物品数量
cin >> s;
//组中每个物品的属性
for (int j = 1; j <= s; j++) cin >> c[j] >> w[j];
//先枚举每一个背包--从大到小,因为这个这个组种的物品只有一个
// 下面的语句是说,这个背包只能装这个组中的一个物品,寻找最划算的
for (int j = V; j >= 0; j--)
for (int k = 1; k <= s; k++)
if (j >= c[k])
f[j] = max(f[j], f[j - c[k]] + w[k]);
// 由于每组物品只能选一个,所以可以覆盖之前组内物品最优解的来取最大值
}
(7)主件背包问题( 必选主件)
int main() {
cin >> n >> V; //n主件个数,V总钱数
// 在背包问题中,f都是初始化为0的
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; i++) {
//继承上一组的情况
memcpy(f_last, f, sizeof(f_last));
// 相当于实现第二次背包
cin >> v >> m; //v主件费用,m附件个数
// 这个是先遍历这个组中的每一个物品,所以物品可以多选
for (int k = 1; k <= m; k++) {
cin >> c >> w; //c附件费用、w附件价值
for (int j = V - v; j >= c; j--)
f_last[j] = max(f_last[j], f_last[j - c] + w); // 这是现在要用的f数组
}
for (int j = v; j <= V; j++) f[j] = max(f[j], f_last[j - v]); // 将得到的f_last数组给f数组
}
cout << f[V];
}
背包的详细讲解
DP原理
将大问题分解成更简单的子问题,大问题由子问题叠加推到而来,整体问题的最优方案由子问题的最优方案得到而来
DP核心---树
需要的有:
1、状态转移方程 (问题推导而来)
2、数组f代表的是什么 (难点)
3、数组f的初始化
解题方式:(如果是模板题直接使用上面的代码)
观察题目,模拟题目的步骤。(这里需要注意对象,也就是你模拟的对象是什么东东,比如一整个数组n x m 还是说 n x m 中的一个点
画出树的结构,看看这个状态可以由前面的状态怎么得到,得出状态转移方程。
最后确定方向,从上到下(递归方式写),还是从下到上(递推方式写)。
DP题目(常用来解决方案数和最值问题)
1、行走类:
菲列波数列、台阶问题
2、序列类:
(1)最长公共子序列
问题描述:给定两个序列X(长度n)和Y(长度m),找出X和Y的最长公共子序列
解答:这里使用d[i][j]表示序列Xi和Yj的最长公共子序列,d[n][m]就是我们要的答案。
状态转移方程:当xi == yj 时候,d[i][j] = d[i - 1][j - 1] + 1;
当不相等时候,d[i][j] = max(d[i][j - 1], d[i - 1][j]);
练习:hdu1159
(2)最长递增子序列
练习:hdu1257
3、其他:
(1)最小划分
问题描述:给出一个数组,把它分成S1和S2两个部分,使得S1的和与S2的和的差值最小。比如[1, 6, 11, 5] 最小划分是S1 = [1, 5, 6] ,S2 = [11] 最小差值是 | 11 - 12 | = 1;
解答:可以将其转化为背包问题,先求出数组的sum,背包的容量为sum / 2, 把数组中的每一个数字看成物品,然后取出最好的结果。
练习:lintcode724
(2)矩阵最长递增路径
练习:leetcode 329
(3)子集和问题
问题描述:给定一个集合{6,2,9,8,3,7},M=5,是否存在一个子集,使得子集的和等于5;
解答:vector<int> v[n]; v[i]表示i个元素里面的集合的子集的和,不断把当前的i与前面i - 1的v进行搭配,最后得到v[n]的数,判断这个数组里面有没有5这个元素。
(4)最优游戏策略
(5)矩阵链乘法(略)
poj 1651
课后题目练习:
洛谷P1216 / 1020 / 1091 / 1095 / 1541 / 1868 / 2679 / 2501 / 3336 / 3558 / 4158 / 5301
poj 1015 / 3176 / 1163 / 1080 / 1159 / 1837 / 1276 / 1014