Dynamic programming

算法 DP

DP(英文 :Dynamic programming)是ACM竞赛中常见的一类题型,又名动态规划

动态规划性质

最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。对此我们可以假设:C到B的最优路径为10,次优为11。在取C到B次优的情况下得到整体优是C到B到A,必然有C到B的最优解使得路径更小。

子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。对此我们可以利用数组等存储结构进行存储已计算的数据。例如计算Fibonacci数列,若采用递归算法,随着N的增大,时间复杂度为指数性。如果采用一个数组存储,计算第N项只需调用N-1和N-2项。

无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

动态规划和贪心区别

DP帮原问题分解成相对简单的子问题,通过对子问题的求解的积累(对若干个子问题求解)来得到原问题的答案,与之相类似的则是贪心算法(英文:greedy)。两者都是帮大问题分解为若干个子问题,通过对子问题的求解的积累来进一步得出原问题的最优解(或局部最优解)。而两者的差别就是如此。DP得到的是整个问题的最优解(可以确定最终得到的算法的解比任何一个解都优)。贪心得到的此问题解比此问题的一部分解优,但有可能不符合最优。
倘若用函数来说明:DP的得到的是这个函数的最大值,贪心得到的是这个函数的极大值。
产生这样区别的原因:对于子问题的求解,后一子问题受前一子问题的影响。举个简单例子:

重量价值物品
2030A
1011B
912C

首先,可以帮ABC三者分为若干份。你的背包承重30。对于此,需要选择相同质量中价值最大的物品,在选次者,直至装满。总价值=30+12+11/10。
现在,ABC均不可分割。在对ABC进行贪心,无论是对总价值最大进行贪心还是对单位重量价值最大进行贪心,得到的都是32。而结果是选择3个重量为10所得到的价值最大。

动态规划题型

一,简单DP

直接看出状态转移方程 hdu2048
从塔顶到塔底最大路径可以看成从塔底到塔顶最大路径。第i,j行可以看成第[i-1][j]和[i-1][j-1]到其最大的,然后再次计算第[i-1][j]和[i-1][j-1]的最大值,递推到最初。
dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+value[i][j];
数学思想如:Fibonacci
可以看出到N号房间只能通过N-1和N-2号房间。dp[n]=dp[n-1]+dp[n-2];

二,背包问题

01背包poj3624
面对一个物体只有选或者不选两种可能,因此可以用状态0或1来表示。a[i%2][j]=max(a[(i-1)%2][j],a[(i-1)%2][j-b[i][0]]+b[i][1])横坐标表示数量,纵坐标表示价值看选了之后的价值是否比没选这个的价值高。a[(i-1)%2][j]表示没选时的价值,a[(i-1)%2][j-b[i][0]]+b[i][1]表示选后的价值。
对于此问题,由于给了内存限制,可以使用滚动数组。可以使用两行数组轮流更新,也可以一行数组从后向前更新。
拓展倘若问题中价值过小体积过大,可以以价值为纵坐标,体积为横坐标选择。
完全背包poj1834
面对一个物体,你有选(选N个)或不选的可能。对于此:你可以帮此物体看成有N+M(M,N为任意数)个,对于每个物体进行01判断。这是可以看成01背包的拓展。dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
多重背包hdu2844
多重背包相对于完全背包区别就是N+M固定,完全背包的N+M在多重背包中由于题意(绝大部分是题意)或其他原因为一个固定的常数,也就是每个物体的数量是固定不变的。在这些固定数量的背包中进行01选择,也是01背包的拓展。
这题dp数组存的不是价值什么的,而是背包的数量(硬币的数量)也就是dp数组当作了一个计数器,也就方便了我们的做题。由于一个转移方程可以不能讲清楚,就直接上代码。

for (int i = 1; i <= n; ++i){
    for (int j = 0; j <= m; ++j){
      if (dp[i - 1][j] >= 0)
        dp[i][j] = C[i];
      else
        dp[i][j] = -1;
    }
    for (int j = 0; j <= m - A[i]; ++j)
      if (dp[i][j] >= 0)
        dp[i][j + A[i]] = max(dp[i][j + A[i]], dp[i][j] - 1);
  }

a[i]代表这种硬币的面值,c[i]代表这种硬币的数量,对于第i种面值的硬币,在没加入第i种面值硬币前先判断,如果dp[i-1][j]>0,意味着不用第i种硬币都可以凑成j值,因为dp代表的是硬币剩余数量,那么dp[i][j]=c[i]。如果dp[i-1][j]=-1,那么凑不成j这种面值,就待定为-1。同时dp[0]置位值c[i]。
后,因为是从0开始,对于dp[i]<=0的就跳过,dp<0是即使有这种硬币也凑不成j数值,dp=0意味这这种硬币用完了,跳过。倘若能 j 凑成 j +a[i],就看凑成后哪个剩下第i种纸币的数量多,取多的一个,也可以强行说一波这是贪心,因为同样价值下剩下的纸币越多,就能组成更多的价值。

while (scanf("%d%d", &n, &m) == 2 && (n + m)){
		for (int i = 1; i <= n; ++i)
			scanf("%d", &A[i]);
		for (int i = 1; i <= n; ++i)
			scanf("%d", &C[i]);
		for (int i = 1; i <= m; ++i)
			dp[i] = -1;
		dp[0] = 0;
		for (int i = 1; i <= n; ++i){
			for (int j = 0; j <= m; ++j){
				if (dp[j] >= 0)
					dp[j] = C[i];
				else
					dp[j] = -1;
			}
			for (int j = 0; j <= m - A[i]; ++j){
				if (dp[j] >= 0)
					dp[j + A[i]] = max(dp[j + A[i]], dp[j] - 1);
			}
		}
		ans = 0;
		for (int i = 1; i <= m; ++i){
			if (dp[i] >= 0)
				ans++;
		}
		printf("%d\n", ans);
	}
	return 0;

因为题中对内存有限制,所以要帮二维数组压缩成一维数组。对于这种第i-1行无法影响第i行的题目或者说再替换之后无法影响,直接滚动数组就可以满足。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值