动态规划经典例题

2023-11-18

  1. 01背包问题
    有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
    限制条件:1 <= n <=100, 1 <= wi, vi<=100, 1 <= W <= 10000
    首先使用最朴素的方法,针对每个物品是否放入背包进行搜索。
/*
4 5
2 3
1 2
3 4
2 2

*/
#include <iostream>
using namespace std;
int w[105], v[105], n;
int f(int i, int j)
{
	int res;
	if (i == n)
	{
		//没有剩余物品 
		res = 0;
	}
	else if (j < w[i])
	{
		//无法选 
		res = f(i + 1, j);
	}
	else
	{
		//选与不选 
		res = max(f(i + 1, j), f(i + 1, j - w[i]) + v[i]);
	}
	return res; 
}
int main()
{
	int W;
	cin >> n >> W;
	for (int i = 0; i < n; i++)
	{
		cin >> w[i] >> v[i];
	}
	cout << f(0, W) << endl;
	return 0;
} 

这种方法的搜索深度是n,而且每一层的搜索都需要两次分支,最坏就需要O(2^n)的时间,当n比较大时就没办法解了。下面是f的递归情况:
在这里插入图片描述
可以看到f(3, 2)调用了两次。下面用记忆化搜索来解这道题,只需要做一点小小的改变。
增加一个二维dp数组记录选了i个物品背包还剩余j重量,dp[i][j]的值则记录其价值。

#include <iostream>
#include <cstring>
using namespace std;
int w[105], v[105], n, dp[105][105];
int f(int i, int j)
{
	if (dp[i][j] >= 0)
	{
		return dp[i][j];
	}
	int res;
	if (i == n)
	{
		//没有剩余物品 
		res = 0;
	}
	else if (j < w[i])
	{
		//无法选 
		res = f(i + 1, j);
	}
	else
	{
		//选与不选 
		res = max(f(i + 1, j), f(i + 1, j - w[i]) + v[i]);
	}
	return dp[i][j] = res; 
}
int main()
{
	int W;
	cin >> n >> W;
	memset(dp, -1, sizeof(dp));
	for (int i = 0; i < n; i++)
	{
		cin >> w[i] >> v[i];
	}
	cout << f(0, W) << endl;
	return 0;
} 

下面我们来研究一下这个记忆化数组dp,从第i个物品开始挑选总重小于j时,有如下递推公式:
在这里插入图片描述

#include <iostream>
#include <cstring>
using namespace std;
int w[105], v[105], dp[105][105];
int main()
{
	int W, n;
	cin >> n >> W;
	for (int i = 0; i < n; i++)
	{
		cin >> w[i] >> v[i];
	}
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= W; j++)
		{
			if (j < w[i])
			{
				dp[i + 1][j] = dp[i][j];
			}
			else
			{
				dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
			}
		}
	}
	cout << dp[n][W];
	return 0;
} 
  1. 最长公共子序列问题
    给定两个字符串s1,s2…Sn和t1,t2…tn。求出这两个字符串最长的公共子序列的长度。字符串
    s1s2“Sn的子序列指可以表示为ss…S(ii…<ian)的序列。
    下面是状态转移方程:
    在这里插入图片描述
#include <iostream>
using namespace std;
int dp[105][105];
int main()
{
	string s, t;
	cin >> s >> t;
	for (int i = 0; i < s.length(); i++)
	{
		for (int j = 0; j < t.length(); j++)
		{
			if (s[i] == t[j])
			{
				dp[i + 1][j + 1] = dp[i][j] + 1;
			}
			else
			{
				dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
			}
		}
	} 
	cout << dp[s.length()][t.length()];
	return 0;
 } 
  1. 完全背包问题
    有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每个物品可以挑选任意多件。

递推关系:
在这里插入图片描述

核心代码:

for (int i = 0; i < n; i++)
{
	for (int j = 0; j <= W; j++)
	{
		for (int k = 0; k * w[i] <= j; k++)
		{
			dp[i + 1][j] = max(dp[i + 1][j], dp[i + 1][j - k * w[i]] + k * v[i]);
		}
	}
}

这里用了三重循环,时间复杂度为o(nW^2)。其实可以不用关于k的循环,用O(nW)时间就可以解决问题。下面是推导过程(我没有太看懂,记住核心代码就行了):
在这里插入图片描述
核心代码:

for (int i = 0; i < n; i++)
{
	for (int j = 0; j <= W; j++)
	{
		if (j < w[i])
		{
			dp[i + 1][j] = dp[i][j];
		}
		else
		{
			dp[i + 1][j] = max(dp[i][j], dp[i + 1][j - w[i]] + v[i])
		}
	}
}

这个代码和上面0背包问题只有一个地方不同。
在01背包当中:dp[i + 1][j] = max(dp[i][j], dp[i ][j - w[i]] + v[i])
在完全背包当中:dp[i + 1][j] = max(dp[i][j], dp[i + 1][j - w[i]] + v[i])

此前提到的01背包问题和这里的完全背包问题,可以通过不断重复利用一个数组来实现。
01背包:

for (int i = 0; i < n; i++)
{
	for (int j = W; j >= w[i]; j--)
	{
		dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
	}
}
cout << dp[W] << endl;

完全背包:

for (int i = 0; i < n; i++)
{
	for (int j = w[i]; j <= W; j++)
	{
		dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
	}
}
cout << dp[W] << endl;
  1. 背包问题之二
    有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
    限制条件:1 <= n <=100, 1 <= wi <=10^7, 1 <= vi <=100, 1 <= W <= 10^9

分析:
这个问题与最初的01背包向题相比, 只修改了限制条件的大小。在这个问题中,相比较重量而言,价值的范围比较小,所以可以试着改变DP的对象。之前的方法中,我们用DP针对不同的重量限制计算最大的价值。这次不妨用DP针对不同的价值计算最小的重量。
递推方程:
在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
int w[105], v[105], dp[105][105];
int main()
{
	fill(dp[0], dp[0] + 105, 0x3f3f3f3f);//不存在时都是无穷大 
	dp[0][0] = 0;//前0个物品什么都挑选不了 
	int W, n;
	cin >> n >> W;
	int MAX_V = -1;
	for (int i = 0; i < n; i++)
	{
		cin >> w[i] >> v[i];
		if (v[i] > MAX_V)
		{
			MAX_V = v[i];
		}
	}
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= n * MAX_V; j++)
		{
			if (j < v[i])
			{
				dp[i + 1][j] = dp[i][j];
			}
			else
			{
				dp[i + 1][j] = min(dp[i][j], dp[i][j - v[i]] + w[i]);
			}
		}
	}
	int res = 0;
	for (int i = 0; i <= n * MAX_V; i++)
	{
		if (dp[n][i] <= W)
		{
			res = i;
		}
	}
	cout << res << endl;
	return 0;
} 
  1. 多重部分和问题
    有n种不同大小的数字ai,每种各mi个。判断是否可以从这些数字之中选出若干使它们的和恰好为K。

分析:dp[i +1][j]=用前种数字是否能加和成j。

#include <iostream>
/*
3 17
3 3
5 2
8 2
*/
using namespace std;
int a[105], m[105];
int dp[105][105];
int main()
{
	int n, k;
	cin >> n >> k;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i] >> m[i];
	}
	dp[0][0] = 1;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= k; j++)
		{
			for (int k = 0; k <= m[i]; k++)
			{
				if (j >= k * a[i])
				{
					dp[i + 1][j] |= dp[i][j - k * a[i]];
				}
			}
		}
	}
	if (dp[n][k])
	{
		cout << "Yes" << endl;
	}
	else
	{
		cout << "No" << endl;
	}
	return 0;
} 

这样做时间复杂度为O(KΣimi),下面降低时间复杂度,定义状态:
dp[i+1][j]=用前 i 种数加和得到 j 时第种数最多能剩余多少个(不能加和得到的情况下为-1)
递推式:
在这里插入图片描述

#include <iostream>
#include <cstring>
using namespace std;
int a[105], m[105];
int dp[105];
int main()
{
	memset(dp, -1, sizeof(dp));
	int n, k;
	cin >> n >> k;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i] >> m[i];
	}
	dp[0] = 0;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j <= k; j++)
		{
			if (dp[j] >= 0)
			{
				dp[j] = m[i];
			}
			else if (j < a[i] || dp[j - a[i]] <= 0)
			{
				dp[j] = -1;
			}
			else
			{
				dp[j] = dp[j - a[i]] - 1;
			}
		}
	}
	if (dp[k] >= 0)
	{
		cout << "Yes" << endl;
	}
	else
	{
		cout << "No" << endl;
	}
	return 0;
} 
  1. 最长上升子序列问题
    有一个长为n的数列a0,a1,……a-1。请求出这个序列中最长的上升子序列的长度。上升子序列指的是对于任意的j都满足ai<aj的子序列。
    分析:定义dp[i]=以a为末尾的最长上升子序列的长度,则递推公式如下:
    在这里插入图片描述
#include <iostream>
using namespace std;
/*
5
4 2 3 1 5
*/
int main()
{
	int n;
	cin >> n;
	int a[50], dp[50] = { 0 };
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	int res = 0;
	for (int i = 0; i < n; i++)
	{
		dp[i] = 1;
		for (int j = 0; j < i; j++)
		{
			if (a[j] < a[i])
			{
				dp[i] = max(dp[i], dp[j] + 1);
			}
			res = max(res, dp[i]);
		}
	}
	cout << res << endl;
	return 0;
}
  1. 有关记数问题的DP
    有n个无区别的物品,将它们划分成不超过m组,求出划分方法数模M的余数。
    限制条件:1≤m≤n≤1000, 2≤M≤10000
/*
4 3
*/
#include <iostream>
using namespace std;
const int M = 10000;
int main()
{
	int n, m;
	cin >> n >> m;
	int dp[50][50] = { 0 };
	dp[0][0] = 1;
	for (int i = 1; i <= m; i++)
	{
		for (int j = 0; j <= n; j++)
		{
			if (j >= i)
			{
				dp[i][j] = (dp[i - 1][j] + dp[i][j - i]) % M;
			}
			else
			{
				dp[i][j] = dp[i - 1][j];
			}
		}
	}
	cout << dp[m][n] << endl;
	return 0;
}
  1. 多重集组合数
    有n种物品,第i种物品有a个。不同种类的物品可以互相区分但相同种类的无法区分。从这些物品中取出m个的话,有多少种取法?求出方案数模M的余数。
    A限制条件:1≤n≤1000,1≤m≤1000,1≤a≤1000,2≤M≤10000
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态规划经典例题 的相关文章

  • Mysql数据库Sql优化

    1 选择合适的字段属性 mysql中表越小 查询速度越快 所以 我们在创建表时 字段尽可能的设置最小 如果可以的话 可以用MEDIUMINT而不是BIGIN来定义整型字段 应该尽量把字段设置为NOTNULL 这样在将来执行查询的时候 数据库
  • 华为OD机试真题- 书籍叠放-2023年OD统一考试(B卷)

    题目描述 书籍的长 宽都是整数对应 l w 如果书A的长宽度都比B长宽大时 则允许将B排列放在A上面 现在有一组规格的书籍 书籍叠放时要求书籍不能做旋转 请计算最多能有多少个规格书籍能叠放在一起 输入描述 输入 books 20 16 15
  • mybatis之foreach用法

    在做mybatis的mapper xml文件的时候 我们时常用到这样的情况 动态生成sql语句的查询条件 这个时候我们就可以用mybatis的foreach了 foreach元素的属性主要有item index collection ope
  • 选择器函数querySelector和querySelectorAll

    选择器是Css非常强大的功能 早先一般是通过getElementById和getElementsByTagName来获取页面元素 在一些场景下就很不方便 后来DOM扩展出了Selector API标准 其中 Selector API Lev
  • 移植uboot-支持yaffs烧写,打补丁

    1 修改uboot支持yaffs 首先 每个命令都会对应一个文件 比如nand命令对应的common cmd nand c 而我们使用nand命令时 便会进入do nand 函数 位于common cmd nand c 1 1do nand
  • 产品经理的思考-ChatGPT的影响

    最近ChatGPT的不断升温 公司开始全面布局和应用人工智能 本以为今年的赋智会有个过渡过程 没想到来的这么凶猛 随着应用的深入 越来越多的开始了灵魂质问 随着大模型的不断深入应用 什么职位会被取代 我们应该如何与ChatGPT共存 Cha
  • 2017年10米分辨率全球土地覆盖产品(FROM-GLC10)Python下载爬虫

    此为2017清华大学地球系统科学系宫鹏教授团队研发的重大成果世界首套 2017年10米分辨率全球土地覆盖产品 FROM GLC10 爬虫下载爬虫分享 一 参考网站 1 全国各省10米分辨率的土地利用数据的制作与分享 2 世界首套2017年1
  • vs2019编译c语言提示有病毒,关于VS2019代码编译的问题(C++)

    代码很长 这里就不打出来了 1 严重性代码说明项目文件行禁止显示状态 错误C2664 BOOL SetFileAttributesW LPCWSTR DWORD 无法将参数 1 从 const char 36 转换为 LPCWSTR Pro
  • android opengl es 总结

    什么是OpenGL ES OpenGL ES 为OpenGL for Embedded System的缩写 为适用于嵌入式系统的一个免费二维和三维图形库 为桌面版本OpenGL 的一个子集 OpenGL ES 定义了一个在移动平台上能够支持
  • Python:每日一题之最少砝码

    问题描述 你有一架天平 现在你要设计一套砝码 使得利用这些砝码可以称出任意 小于等于 N 的正整数重量 那么这套砝码最少需要包含多少个砝码 注意砝码可以放在天平两边 输入格式 输入包含一个正整数 N 输出格式 输出一个整数代表答案 样例输入
  • (CMake) 指定生成器 generator

    文章目录 问题引入 具体处理 当前环境 例子 解决方案 命令行 设置变量 设置win环境变量 END 附录 win cmake 3 24 2 help linux cmake 3 10 2 help cmake基础 CMake 从下载到构建
  • Keil运行stm32项目无法打断点调试

    项目场景 有个新同事接了外协写的STM32F429的项目 项目接过来编译和烧录都没问题 但是Debug调试时候没法打断点 没有灰色区域可以点断点 点击运行可以 但点暂停也没有停止黄色光标 debug模式下就如同这样 1 问题描述 根据上述现
  • 关于APT32C001ADC采集不准的问题说明

    因为之前开发一款产品 要使用到触摸按键 又不想新增一个触摸IC 所以选择了APTC001进行开发 但是在调试的时候发现ADC有时候会不准 有时候是0电压的 但读寄存器的值却不是零 有时候读电源电压 那应该是4096的 但实际采集回来的去不是
  • 【毕业设计】机器视觉手势检测和识别系统 - python 深度学习

    文章目录 0 前言 1 实现效果 2 技术原理 2 1 手部检测 2 1 1 基于肤色空间的手势检测方法 2 1 2 基于运动的手势检测方法 2 1 3 基于边缘的手势检测方法 2 1 4 基于模板的手势检测方法 2 1 5 基于机器学习的
  • 浅述SATA接口Raid、AHCI、IDE三种模式

    今天在一台计算机上插上CF卡 不能工作 CF卡灯不亮 进BIOS SATA mode从IDE改成AHCI就好了 首先说一下 关于主板的SATA接口的工作模式 BIOS中常见的选项有以下三种 RAID 部分技嘉主板叫XHD AHCI IDE
  • Java String 常用操作方法说明和使用

    ps Java中的String类是一个非常重要的类 在Java程序中广泛使用 它可以用来保存和操作字符串 在这篇博客中 我们将对Java String的所有操作方法进行说明和使用 1 1 使用双引号创建字符串 String str1 Hel
  • ObjectC基础之注释、关键字、数据类型

    一 OC的注释 OC的注释不是 或者 了 它的注释是 举个例子 这是被注释掉的内容 二 OC的关键字 上图我们比较陌生的有 register typedef extern union unsigned const signed goto v
  • 自定义函数实现字符串处理函数strcat、 strcpy、strcmp、strlen和strlwr

    编C语言程序 用自定义函数实现字符串处理函数strcat strcpy strcmp strlen和strlwr的功能 strlen char str int n 0 char p str while p n return n strcat
  • 蓝桥杯 题库 简单 每日十题 day2

    01 卡片 题目描述 本题为填空题 只需要算出结果后 在代码中使用输出语句将所填结果输出即可 小蓝有很多数字卡片 每张卡片上都是数字 0 到 9 小蓝准备用这些卡片来拼一些数 他想从 1 开始拼出正整数 每拼一个 就保存起来 卡片就不能用来

随机推荐