动态规划算法(DP)

     动态规划算法采用分治算法的思想,将原问题分成若干个子问题,然后分别求解各个子问题,最后将子问题的解组合起来得到原问题的解。分治算法递归地求解各个子问题,可能重复求解某些子问题。与分治算法不同的是,动态规划算法不是递归地求解各个子问题,它是从简单问题的解入手,逐步求解,直至求解出原问题。动态规划算法的高明之处在于,它不会重复求解某些重复出现的子问题,即重叠子问题。

01背包:

    有N件物品和一个容量为V的背包。第 i 件物品的费用是c[i],价值为w[i]。求解将那些物品放入背包可使价值总和最大。

基本思路:

    这是最基本的背包问题,特点是:每种物品仅有一件,可以放或者不放。

    用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程就是:

                 f[i][v]=max(f[i - 1][v] , f[i-1][v - c[i]] + w[i] )。

HDU 2602

代码:

#include<stdio.h>
int dp[1010][1010];
int max(int x,int y)
{
	if(x>=y)
		return x;
	else
		return y;
}
int main()
{
	int t,n,m,i,j;
	int value[1010],v[1010];
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(i=1;i<=n;i++)
			scanf("%d",&value[i]);
		for(i=1;i<=n;i++)
			scanf("%d",&v[i]);
		for(i=0;i<m;i++)
			dp[1][i]=0;
		for(i=1;i<=n;i++)
			for(j=0;j<=m;j++)
			{
				if(j<v[i])
					dp[i][j]=dp[i-1][j];
				else
					dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+value[i]);
			}
		printf("%d\n",dp[n][m]);
	}
	return 0;
}

完全背包:

     有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值为w[i],求解将那些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本思路:

     这种问题非常类似于01背包问题,所不同的是每种物品有无限件。从拿取角度来看,策略并不是取或不取,而是取0件,取1件,取2件......等等很多种。

     状态转移方程为:  f[i][v]=max(f[i - 1][v],f[i][v - c[i]] + w[i]).

代码:

#include<stdio.h>
#include<string.h>
int dp[110],w[110],v[110];
int main()
{
	int n,m,i,j;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		memset(dp,0,sizeof(dp));
		for(i=1;i<=n;i++)
			scanf("%d%d",&v[i],&w[i]);
		for(i=1;i<=n;i++)
			for(j=v[i];j<=m;j++)
				dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
		printf("%d\n",dp[m]);
	}
	return 0;
}


多重背包问题:

      有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

      这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i] | 0<=k<=n[i]}。

### 动态规划算法及其 dp 数组的使用方法 动态规划(Dynamic Programming, DP)是一种通过将复杂问题为更小的子问题决优化问题算法技术。它通常用于求具有重叠子问题和最优子结构性质的问题。在动态规划中,`dp` 数组是一个关键的数据结构,用于存储子问题,避免重复计算。 #### 1. 动态规划的核心思想 动态规划的核心思想是将一个问题拆分为多个子问题,并通过递推关系从子问题逐步构建出原问题。这种思想可以显著减少计算量,提高算法效率[^1]。 #### 2. `dp` 数组的作用 `dp` 数组是用来存储子问题的数组或矩阵。它的每个元素代表某个子问题。例如,在划分数组问题中,`dp[i][rest]` 表示在前 `i` 个元素中选择若干个数使得它们的累加和最接近但不超过 `rest` 的最大值[^1]。 #### 3. 动态规划的基本步骤 - **定义状态**:确定 `dp` 数组的含义,明确每个元素表示什么。 - **状态转移方程**:根据问题的性质,设计从一个状态转移到另一个状态的规则。 - **初始化**:设置 `dp` 数组的初始值,通常是边界条件。 - **遍历顺序**:按照正确的顺序填充 `dp` 数阵列,确保每个状态都可以从已经计算过的状态中得出。 - **返回结果**:从 `dp` 数组中提取最终答案。 #### 4. 示例代码析 以下是一个经典的动态规划问题——计算不同路径的数量的代码析: ```go func uniquePaths(m int, n int) int { dp := make([][]int, m) for k := range dp { dp[k] = make([]int, n) } for i := 0; i < m; i++ { dp[i][0] = 1 // 初始化第一列 } for j := 0; j < n; j++ { dp[0][j] = 1 // 初始化第一行 } for i := 1; i < m; i++ { for j := 1; j < n; j++ { dp[i][j] = dp[i-1][j] + dp[i][j-1] // 状态转移方程 } } return dp[m-1][n-1] // 返回最终结果 } ``` 在这个例子中,`dp[i][j]` 表示从左上角到 `(i, j)` 的不同路径数量。通过状态转移方程 `dp[i][j] = dp[i-1][j] + dp[i][j-1]`,我们可以从已知的状态推导出未知的状态[^3]。 #### 5. `dp` 函数与 `memo` 数组的关系 除了使用 `dp` 数组外,动态规划还可以通过递归结合记忆化搜索实现。在这种情况下,`memo` 数组用于存储递归过程中计算出的子问题,避免重复计算。`memo` 数组的更新通常发生在递归函数的最后一步,确保每个子问题都是准确且完整的[^2]。 #### 6. 滚动数组优化 在一些动态规划问题中,`dp` 数组的空间复杂度可以通过滚动数组技术进行优化。例如,在二维 `dp` 数组中,如果状态转移只依赖于前一行的数据,则可以将二维数组压缩为一维数组,从而节省空间[^3]。 ### 总结 动态规划是一种强大的算法技术,其核心在于利用 `dp` 数组或 `memo` 数组存储子问题,避免重复计算。通过合理定义状态、设计状态转移方程以及初始化 `dp` 数组,可以高效地决许多复杂的优化问题。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值