适合新手入门的DP题总结【精选洛谷题集30道】

23 篇文章 0 订阅

0.总结

  • 简述解答dp问题时常用的思想
  • 列举笔者刷题时遇到的较为简单的DP练习,已经对这些题目的思考【题目主要来自洛谷】
  • 文章来源:CSDN@LawsonAbs

1.主要思想

动态规划(dp)问题是编程从入门到进阶的一个分水岭,可以这么说:几乎任何一份有质量的题中都有dp的影子。【当然,我这里的dp指的是广义上的递推】这也就是数学中的逻辑思维的体现,将得到的公式敲出代码实现,就是物理中的实证思维的体现。所以说编程能够体现一个IT人员的基本素养,而dp问题则是能力的主要试金石。这里笔者总结了常见的dp入门题,希望能够帮助到大家。如下是我总结出处理dp问题时,总结到的一些经验。

  • 抽象看问题! 从一个问题中,确定出哪些因素是关键因素?是什么导致问题的变化?是什么导致状态的变化?

  • 转换问题。解题时,尽量把题目转化为已学的模型,比如背包;LCSLCIS等等

  • dp中,不同的操作体现在对维度的操作上。对各个维度取不同值时选择其最优解。dp问题无非就是找出相应的维度。

  • 如何确定问题的状态?
    问题通常由哪些要素决定?由几个要素决定,通常就是有几种维度。比如说:01背包问题,由重量和物品数两个维度决定,那么我们设计的dp数组就是一个二维的;在最长公共子序列中,

  • 如何确定状态的转移方程?
    将一个问题拆分成子问题的时候,我们可以从子问题得到父问题的解,根据这个解的表达式,就可以得到状态的转移方程。

2.案例分析

2.1 P1439【模板】最长公共子序列

  • step1.离散化处理第一个序列
  • step2.根据第一个序列离散化的结果求出第二个序列最长不下降子序列的长度即可。

2.2 P1103 书本整理

  • step1.确定问题是什么,再想能否转化问题。
    就这题来说,可以知道,其实题目就是要求出,在去掉k本书之后,得到剩余序列相邻元素间差的绝对值的和的最小值。
  • step2.转换问题之后,其实去掉k本书,就相当于选n-k本书。
  • step3.选哪本书是一个操作,操作用于决定维度。这时就可以猜测书的序号是否是一个维度。【比如选第i本应该就是f[i]】
  • step4.上一步骤中选出的书处于什么位置也应是一个维度【自己动手模拟时,就会发现,选不选都会改变书的最终位置】。比如选出的第i本书,处于从左至右的第j本,不同的j取值也不相同。这时就得到了一个二维状态数组f[i][j]。

2.3 P1140 相似基因

这道题讲的是如何求出两个序列的最大相似值。
针对两个序列可以增添一个‘-’字符用于匹配。本题的难点也就是在于这个‘-’字符的处理。但是如果定义好了维度就会发现很简单。
有两个序列,(求最大相似值),是不是让我们想到了LCS,类似的定义,就有f[i][j]表示A1-Ai 与序列B1-Bj 所能达到的最大相似值。于是。

  • step1. 定义f[i][j]表示A1-Ai 与序列B1-Bj 所能达到的最大相似值。
  • step2.对于每个f[i][j]均可由f[i-1][j]【Ai匹配-】,f[i][j-1]【-匹配Bj】,f[i-1][j-1] 【Ai匹配Bj】推导而来

2.4 P1020 导弹拦截

在这里插入图片描述

问题转化,问需要几枚导弹才能把所有的目标都拦截?其实就是给定一个序列,让你求出最长上升子序列的长度。

有几个问题需要注意一下:

  • 导弹可以不连续拦截
    比如序列3 6 2 4 可以分成(3,2),(6,4)分两波拦截,只需要两个导弹。而不是需要(3)(6,2),(4)三个导弹。

3 6 2 3 4
可以这么理解,第二问可以用“最长上升子序列”来求是因为:

  • 对于最长上升子序列中的元素,满足若i<j,有 A i < A j A_i < A_j Ai<Aj。当 A i A_i Ai被之前的导弹击中时, A j A_j Aj必须被另外一个新的导弹击中,如果是同一个导弹击中,是不符合题意的。(导弹不可能先去击中 A j A_j Aj,再去击中 A i A_i Ai,因为是先出现 A i A_i Ai,再有 A j A_j Aj)。同理,对于任何一个最长上升子序列相邻的元素都是这么分析,设其长度为len,则需导弹len个。

  • 最长上升子序列的一些性质也是很有意思的
    【递增情况:(3 4 5)】假设有序列:(3 4 5 1 2 3),其LIS为(3 4 5)或(1 2 3)。可以看到在1位置之前的元素是没有比1更小的了,否则1不会是LIS的第一个元素,(因为我们求的是LIS,若有更小的,肯定会把它算成LIS的一部分)。
    所以可以前面的任何一个炮弹打完之后都可以把1顺带消灭;同理分析LIS中的2,3都是如此。

【递减情况:(5 4 3)】假设有序列:(5 4 3 1 2 3),其LIS为(1 2 3)。很显然,不再分析。

【有增有减(5 3 4)】假设序列(5 3 4 1 2 3),其LIS为(1 2 3)。因为(3 4)并不是LIS,所以一定可以在消灭LIS之前把(3 4)消掉。其余同理分析。

2.5 P1435 回文字串

在这里插入图片描述

这题有两种解决方法,分别是LCS和 区间DP。这里采用LCS来做。

方法1:LCS
主要是问题的转换。原文题等价于求出LCS。至于为什么这么做,我尝试将我的思路说给大家听听。

  • step1.有原串s1 = abdca
  • step2.取其倒序串s2= acdba。【为什么取倒序串?因为要使字符串回文,所以就需要倒着念。】
  • step3.找出s1与s2的LCS。针对上面的字符串,可以发现(aba),(ada),(aca)都可以是LCS。【这个LCS是回文的,因为它们正着读和反着读的结果是一样的】
  • step4.也就是说字符串s1在变成回文串之前已经有LCS是回文的了,令LCS的长度为len,所以相应的只需要针对剩下的n-len个字符即可。

2.6 P1470 [USACO2.3]最长前缀 Longest Prefix

这题其实就是一个完全背包的变题。主要思路如下:
给出一个集合,集合中是若干个字符串,再给出一个字符串s。判断由集合中的元素组成的字符串s的前缀最大长度是多少?
如果将集合中的每个字符串看成是物品,每个字符串可以放无限次。其长度就是该字符串的价值。但是不同于朴素的完全背包,放下物品(字符串)的时候,不仅需要判断之前的前缀是否可达,还要判断接下来若干长度的字符串长度是否匹配。比如测试用例

A AB BA
.
AABAA

在放集合中的第一个元素的时候,最大前缀可达2,在匹配第三个的时候因为A!=B 失败了。
本题需要注意的点是:循环的层次。我曾像完全背包那样使用双层for循环【第一层遍历物品(即这里的集合),第二层遍历价值(即这里的前缀)】,结果却是错误的。正确的是,第一层先遍历价值,第二层再遍历集合。

2.7 P1474 [USACO2.3]Money System / [USACO07OCT]Cow Cash G

类完全背包。
主要思路,依次放入前i个物品,然后判断到达价值j时的方案数,依次累加即可。

2.8 P1481 魔族密码

类似LIS 问题
只不过这里的“上升子序列”的定义方式是:若一个字符串s1的前缀是另外一个字符串s2,则由s2,s1构成的序列则是上升的。
实现的时候需要注意:应该初始化f[]为0,然后在输出的时候输出max(f[i])+1,因为题目要求输出最长单词链中单词的个数。

2.9 P1504 积木城堡

类似0/1背包问题
有n个问题,每个问题都是0/1背包问题。让求出这n个问题中共同的最大的价值(即本题的高度)。

2.10 P1566 加等式

简单的0/1背包问题
只需要判断集合中每个元素可到达的方式种类,最后再减去集合中元素的个数即可。

2.11 P1586 四方定理

本题的问题主要有:

  • 1.如何确保只有4个数以内的平方和到n?
  • 2.如何避免重复?比如25 = 3^2 + 4^2 = 4^2 +3^2 ,但实际上,这两种情况只看做一次。
  • 3.dp题难道只能用dp的算法做吗?
    当然不是,对于本题,我们甚至可以用4重for循环来暴力枚举。

主要方法
方法1:dp
其实本题是一个类完全背包的问题。
设f[i][j]表示 数i可由j个数的平方和表示的个数,其中j取值范围为1-4;转移的方程为:如果f[j-squ[i]][0-3]>0,则 f[j][(0-3)+1] += f[j-squ[i]][0-3]; 这么做是不会造成 25 = 3^2 + 4^2 = 4^2 +3^2 这样的重复解。因为放数字的是有顺序的,【在本题中,也就是按照先放3,再放4的顺序来的】

但是有如下几个技巧:

  • 我们不用对于每个n都去计算其解的个数,这样会导致重复解。相反正确的做法是求出输入数据范围之内的最大值maxT,然后计算maxT内所有整数的解,然后针对输入数据,直接输出结果,否则复杂度会很大。我因为“相信自己思想没错”导致交了数次的TLE代码。就是因为重复解了很多次导致超时。
    这题侧面也说明了要多动脑,少改代码!
  • 在四重for循环中,需要注意剪枝和避免重复解。我们可以按照i<=j<=k<=l 这样的大小关系,枚举四个循环。

bfs可用于求朴素版的最短路。之所以说是朴素版,就是因为其搜索的图中的顶点间的边没有边权(或者说权重值都是相同的);或者是单纯的搜索次数就是“优越性”的比较。但如果在一个不是以走的路径次数为优先的题目中,就很难确定最优解。例如络谷的P1649题。

2.12 P1649 [USACO07OCT]Obstacle Course S

这题打上dp的标签是想说明dp的过程就是不断更新迭代的过程吗?

主要思想

  • 使用bfs不断更新到达坐标(cx,cy)需要转弯的次数。
  • 队列中的数据类型是Node,其包含的元素是坐标(x,y),转弯次数tN,到达该点时的方向dir。
  • 不断的进出队,找到最优解即可。

本题坑点:

  • 不要仅仅以为只有当转弯的次数更低时,才可入队。因为每次入队都是有方向的,不同方向的入队但转弯次数相同时,会对后续的序列造成影响。【这种问题的原因归根结底是:判断一个元素是否更优的条件取决于多个因素(本题不仅取决于转弯次数,还和转弯的方向有关),当你只判断了其中部分因素(我只选择步长更短的入队,而不管其方向),就导致得不到最优解,从而WA! 】

2.13 P1681 最大正方形II

很简单的dp算法
主要思想:

  • f[i][j]指的是坐标点(i,j) 取到的最大值。
    考虑坐标获得在左上方向的最大正方形的边长。【可能有人会问,那么不用考虑右上方向的最大正方形了吗?是的,不用了,因为这两个方向是等价的】转移方程如下:
if(arr[i][j]!=arr[i-1][j] && arr[i][j]!= arr[i][j-1] 
				&& arr[i][j] == arr[i-1][j-1]){
				f[i][j]	= min( min(f[i-1][j],f[i][j-1]),
							f[i-1][j-1]) 
							+1;
			}

2.14 P2004领地选择

主要思路
有两种方法解决这道题目。
方法一:
使用row[i][j]记录 第i行前j列数的和; col[i][j]记录第i列前j行的和。在遍历二维坐标点的时候,找出以该坐标点为方形左上角元素时,得到的最大和。得到方形元素的最大和的方法是:

  • 判断第j列时,将第j-1列所在正方形中的和从cur中去掉,同时加上第j+c-1列的和
  • 判断第i行时,将cur置零。重新获取以坐标(i,1)开始的正方形的和。

方法二
使用二维数组的前缀和。这个很好写,建议使用这种方式。

2.15 P2904 [USACO08MAR]River Crossing S

主要思路:
方法1:
我刚开始拿到这道题,没有想太多。直接写成了贪心。40分!!!因为局部最优得不到全局最优。然后明白应该用之前的每头牛去更新当前的牛所需要的最小价值。
rec[i]表示第i头牛是船中的第几头牛; dp[i]表示第i头牛过河需要的最短时间

方法2:
仔细思考这题,就会发现这题其实是一道简单的背包问题(我更倾向于认为这是个0/1背包)。将一次运送1,2,……i头牛的代价分别记为d[i],则需要找出一个运送方案(其实就是加数集合)使得d[n]最小即可。
转移方程就是: d[i] = min(d[i],d[j+]d[i-j]+d[0]);

2.16 P1754 球迷购票问题

问题转换!!
本题和P1044 栈很像!【将售票员拿到50元看成是入栈操作,售票员找零50元看成是出栈操作,于是问题就转换成n个50元有多少个出入栈的次数问题,也就是1-n这n个数,在出入栈之后,有多少个不同的排列次序问题。】
将实际的问题抽象成二维坐标点,然后根据状态的转移,得到递推方程,从而得到解。下面分别分析。
问题1:

  • dp[i][j]代表已经来了i个50的人和j个100的人时的方案数;【问题就在于如何想到是这么定义得?】
    那么分两种情况
if(i>j)
{
    dp[i][j]=dp[i-1][j]+dp[i][j-1];//如果来的50的多于100的,那么这次来50和100的都可以
}
    else if(i==j)
{
    dp[i][j]=dp[i][j-1];//如果100和50的一样多,那么这次只能来50的
}

实现的时候,只需要让i>=j恒成立即可。

  • 我自己定义状态时,还出现了相对 手拿50元的球迷在队中的数目的判断。如果到n了,则停止入队,这是递归的思想,但不是递推。

2.17 P1233 木棍加工

疑问是:
01.为何在将长度排完序之后,从宽度就可以得到最优解了?为何是最优解?

2.18 P2690[USACO04NOV]Apple Catching G

主要思想

  • 令dp[i][j][k] 表示 第i分钟,用j次移动,在树k下 获取的最大值 。知道状态之后,就好搞转移方程了。【一定要先在纸上写下来,再写代码!!!!非常重要】。

坑点:

  • 01.移动的奇偶次数关系很重要!否则结果会多算摘到的苹果。其根本原因是,不是所有情况下的 k与arr[i]相等时,就可以摘到,而要符号条件的移动次数。
  • 02.移动次数要从0开始枚举到w
  • 03.题面说:奶牛不愿意不停地往返于两棵树之间。 不知道是翻译的问题,还是题面的问题,这个要求是没有体现在代码中的。

2.19 P3252 [JLOI2012]树

主要思路
dfs+剪枝
搜索每个节点,如果和到s,则返回,否则继续深搜,直到和超过s,或者到达边界。
剪枝操作是:如果该点的子孙节点的所有和都不能到达s,那么就不用再遍历其子孙节点。

2.20 P2196 挖地雷

主要思路:
深搜即可
给出地窖中的地雷信息,每个地窖的连接信息,求出可以从任一个地窖出发可以排查出的最大地雷数。

2.21 P5146 最大差值

水题一道。
边记录,边找出最小的值,然后用当前的值减去已经找到的最小值。用res记录差值的最大,最后输出res即可。

2.22 P2725 [USACO3.1]邮票 Stamps

这题要变换思路。
将其转换成背包来做。需要注意的问题就是,令dp[i]为到达价值i所需的最小邮票数,而不是dp[i]=1表示价值i可达。【这种令法在dp题中很常见!!】 如果是像后者那么假设,则会得到一个错误的3重for循环。

	for(int i = 1;i<=k;i++){//放第i张 		
		for(int j = maxV;j>=0;j--){//价值j 
			for(int l = 1;l<n;l++){
			...		
			}			
		}		 
	}

做dp题不能死板,要根据题意灵活使用模板。

2.23 P6208 [USACO06OCT]Cow Pie Treasures G

简单的dp题。
直接给出了显性的坐标了,就等于说明这是一道水题了。
主要思路

  • 令dp[i][j]表示坐标(i,j)可达的最大值。
  • 按照先列后行的顺序遍历即可,在遍历过程中,不断的将dp[i][j]得成最大值。

坑点:
并不是所有的点都可以进行移动的,比如说,最开始只有坐标(1,1)可以往右移动,但是(2,1),(3,1)就没有资格往右移动。【所以说即使由(2,1)出发构成最大值也是不能要的。所以用个标记数组标记,只能从已经访问过的坐标开始往右遍历。】

2.24 P3133 [USACO16JAN]Radio Contact G

简单的递推。
需要注意的点有:

  • 1.问题的转换
    根据每个人每次走的方向,得到一个二维坐标的序列。题目的含义就相当于要求每两个坐标点(分别是A,与B)对应上取得的最小值。
  • 2.dp数组初值的设置
    令dp[i][j]表示在A在序列i,B在序列j时,能够达到的最大值。

学会从题目中得到解题信息。比如:

  • 当看到n的值较小时,只有百级别,那么就可以考虑O(n^3)的算法,开数组时,也可以开出多维数组。

2.25 P1103 书本整理

dp题的关键是如何设置状态转移方程。
本题,很多人都能令 dp[i][j]是从前i本书中选出j本书所能够达到的最小不整齐度。但是还有一个关键点是:dp[i][j]表示的是第i本书要被放到这j本书中【也就是作为这j本书的最后一本】。 可能会有人问为什么?但是我想说,为什么要问为什么?

如果dp[i][j] 就是从前i本书中选出j本书。那么当我们更新从前i本书中取出j本书时得到的最小不整齐度时,我们的更新值是需要用当前加入的这本书同上一次放(j-1本)书的宽度值作为新的损耗值加进来,但是这时问题就来了,我们怎么知道上一次放(j-1)本书结尾的那个宽度值?
即使你用数组rec[i][j]表示从前i本书中选出j本书时,第j本书的编号。你只是用这个数组来存储,但是你没有定义它是怎么放的,所以仍然无法确定这个rec[i][j]值该取什么。就拿一个特殊的例子,

rec[1][1] = 1? 
rec[2][1] = 1? 2?
rec[3][1] = 1? 2? 3?
……

所以,必须定义清楚,因为这涉及到dp更新的问题,是个关键问题,在编码之前就必须考虑清楚。

题目中有什么重要元素,就应该考虑将这个重要元素作为一个维度存储起来。

2.26 P2782 友好城市

水题一道。【可供dp新手练习时间复杂度为O(NlogN)的最长不下降子序列长度】
主要思路
还是问题的转换。

  • 要不相交的通航道。从x坐标来看,从小到大的建航道,如果不相交,就等价于南部城市Xi建好之后,其友好城市为Ai;接着建下一个城市Xj,新建的航道城市必须是在城市Ai更靠右的【更靠右的特征就是一个不下降的特征】。从而可以发现其实本题就是让我们在以南端城市顺序从小到大排序之后,求北端城市的一个最长不下降子序列。

2.26 P2049 魔术棋子

不是所有的DP标签题都要用dp解。例如本题。
主要思路
方法一:
用bfs遍历每个点,用set集合存储每个点所能到达的值的集合。【肯定是需要保存的,用于提供给后续的点】

  • 对输入数据预处理,arr[i] %= k;
  • 从初始点开始bfs,每次bfs的时候,对当前点的值集合进行遍历,乘上即将要走到的点B的值,然后放到B的值的集合中。
  • 队列遍历结束时,整个任务就完成了。输出最后一个点的集合即可。

优化的方法:
直接用一个三维数组arr[x][y][z]存储点(x,y)能否到达值z。数组大小也就1e3。所以比用set更好。
bfs可以解决dfs深搜带来的重复问题。【很多题dfs解都会十分复杂,但是bfs则是较优的方案】

方法二:使用dp
dp[x][y][z] 表示在位置(x,y)是否可以得到值z。然后一个简单的三重for循环即可。

for(int i=1;i<=m;i++)
     for(int j=1;j<=n;j++)
      for(int l=0;l<k;l++)    //因为mod k 后得到的数一定小于k,所以从0到k枚举 
       if(!dp[i][j][l*num[i][j]%k])        //没有计算过 
        dp[i][j][l*num[i][j]%k]=dp[i-1][j][l]||dp[i][j-1][l];    //l*num[i][j]%k表示当前格子数乘从左边或上边传下来的数l再mod k,dp[i-1][j][l]和dp[i][j-1][l]表示在上方或左方能不能得到l 

方法三:深搜
如果想用深搜,恐怕朴素的深搜不大好使【不好剪枝(我太菜),只有20分】。分太少,主要是因为打开深搜的方式不对。
同上面的定义,判断点(x,y)在取值z时是否访问过,如果访问过,则不再深搜,否则深搜,这样就可以避免重复的搜索了。这要求我们和dp解题一样,定义一个数组为:vis[x][y][z],深搜时注意重复即可。

可以看到无论是深搜,还是广搜,还是dp,其实质上的数组维度都是一样的,都是三维【在set版本的bfs中,其set相当于第三维】。这些是巧合吗?

2.27 P3399 丝绸之路

简单的dp题,仔细分析一下,有点儿像背包,但实质上不是背包。
可以发现,问题就是在M天内到达城市N,求最小的疲劳度。根据求什么设什么的原则,我们可以令dp[i][j] 为前i天到达城市j,花费的最小疲劳度 。【求什么设什么是一个高度总结,新手可能会问为什么,但是仔细想想,就应该是这样。】
主要思路

  • dp[i][j] 为前i天到达城市j,花费的最小疲劳度
  • 用前一天的值与当前的值做比较,并始终取最小值即可。循环中的主要语句如下:
dp[i][j] = dp[i-1][j];
dp[i][j] = min(dp[i][j], dp[i-1][j-1]+ d[j]*c[i] );

2.28 P2426 删数

本题要转化问题。感觉洛谷的题都是这个样子,

2.29 P1833 樱花

背包问题的组合题。【多重背包】该物体有使用次数限制,【完全背包】该物品可以无限次使用。

针对题意可以有如下朴素思路:

  • 1.如果物品是无数个数目,则用完全背包实现
  • 2.如果物品是有限个数目,则用多重背包实现
    但是这样的实现复杂度很高,可以考虑采用 二进制拆分,将一个物品拆成若干个类似物品,最后再用0/1背包解决问题。

2.30 P1004 方格取数

看到数N的取值范围是<=9。那么就可以想到是否可以有很多重循环来解决这个问题。几乎都是这样的,事实证明这题的朴素做法就是要用一个四维数组来搞。
主要思路:

方法1

f[i][j][k][l] 表示两个人到坐标(i,j),(k,l)所能达到的最大值。然后不停的进行更新即可。需要注意的是,如果(i,j) = (k,l),那么就不能加两倍的数。
方法2:

  • 找阶段
    可以发现两种行走路径所到达的终点坐标(x1,y1),(x2,y2) 存在如下关系:
    x1+y1 = x2 + y2。同时,令i为行走的步数【步数是依次增加的,可以将其看做是DP中的阶段】,那么同时也有:x1+y1 = x2 + y2 = i+2。
  • 找状态
    于是我们可以用 (i,x1,x2) 来唯一表达出一个“状态”。这个状态的含义就是“在行走了i步之后,分别到达(x1,y1),(x2,y2)所能达到的最大值”
  • 找决策
    这题中的决策很容易就能发现,分别是走法。两个路径分别有两种走法,合计就是4种走法。对于这个决策我们不需要使用for循环来表示,相反直接使用几个if循环判断就可以了。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

说文科技

看书人不妨赏个酒钱?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值