动态规划DP

代码(工具)

类型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 

  • 23
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值