最后的答案必须是max{dp[m][k]}注意k的范围是m<=k<=n。
这样裸的复杂度是O(mn^2)的明显超时,在转移过程中进行优化,记录ma表示dp[i-1][i-1]到dp[i-1][j-1]的最大值,直接O(1)的得到max{dp[i-1][k]}的值。另外要使用滚动数组来防止空间复杂度过高,这样空间复杂度从O(nm)降至O(n)。优化后的时间复杂度是O(nm)的。
HDU1074
状态压缩dp加记忆化搜索。记录dp[st]表示到达st这个状态的时候的最少扣分。
dp[st]=min{dp[k]}其中k状态可以到达st状态,记录一个tp数组,tp[st]表示到达st这个状态时需要花费的时间,这个数组在O(2^n*n)的时间内预处理出来。
另外还要有两个辅助数组pre和id,pre[st]表示状态st的前一个状态,id[st]表示完成st状态的最后一个科目的id,注意由于要输出最小字典序解,所以字符串从大到小排列(为了dfs的时候反过来输出是最小字典序)。
HDU1078
很囧的题目没看懂,实际上就是给你一个迷宫,每次只能向权值较高的地方走,走的限制是每次只能朝四个方向的其中一个走至多k步,问你可以得到的最大价值。
明显的dp加记忆化搜索。直接定义dp[i][j]表示以(i,j)这个格子为起点能得到的最大价值,注意由于每次只能走向权值较大的点,相当于是一个DAG,肯定不存在回路的,所以直接转移即可。
HDU1158
定义dp[i][j]表示第i个月有j个人的时候的最小费用,这样dp[i][j]就可以通过dp[i-1][k]中的有效状态转移过来,dp[i][j]=min{dp[i][k]+cost}+j*salary。关于初始化,对全部i>=1,将dp[0][i]设为oo表示不可达的状态,这样我们就能保证第一个月是通过dp[0][0]转移来的。最后结果取dp[n][k](k>=c[n])的最小值。
这里为了防止无上界现象,我们去max{c[i]}为第二维的上界,可以很容易证明对于k>max{c[i]}的dp[i][k]肯定不会是最优解,也不会是最优子问题。
HDU1165
这题不能用记忆化搜索或者迭代,因为你并不知道n可以到多大,题目只说了dp[i][j]的值会在某个范围内,所以我们进行公式推导。
m=0时dp[m][n]=n+1
m=1时,由于dp[m][0]=dp[m-1][1]=2,并且dp[m][n]=dp[m-1][dp[m][n-1]]=dp[m][n-1]+1,所以有dp[m][n]=n+2
m=2时,dp[m][0]=dp[m-1][1]=3,并且dp[m][n]=dp[m-1][dp[m][n-1]]=dp[m][n-1]+2,所以有dp[m][n]=2*n+3
m=3时,dp[m][0]=dp[m-1][1]=5,并且dp[m][n]=dp[m-1][dp[m][n-1]]=2*dp[m][n-1]+3,所以有dp[m][n]=8*2^n-3
CDOJ1303
POJ1160的加强版,首先我们要做的事情是对在(i,j)区间建立邮局的最小距离和进行优化,其实这个计算可以在O(n^2)内完成。利用下列的推导即可:
let k = (i+j)/2
w[i][j]=(p[k]-p[i])+(p[k]-p[i+1])+...+(p[k]-p[k-1])+(p[k+1]-p[k])+...+(p[j]-p[k])
=p[k]*(k-i)-p[k]*(j-k)+(p[k+1]+p[k+2]+...+p[j])-(p[i]+p[i+1]+...+p[k-1])
=p[k]*(2*k-i-j)+(sum[j]-sum[k])-(sum[k-1]-sum[i-1])
这样我们就得到了关于sum的一个式子,这里的sum[i]表示从1到i的地点的坐标和。
然后我们观察状态转移的式子
dp[i][j]=min{dp[i-1][k]+w[k+1][j]}
这里我们可以证明对任意的i'和j’,如果i
详细可以参考代码。
HDU1428
图论加dp,首先用spfa预处理出最短路,然后记忆化搜索dp。只要下一个结点到目的地的距离小于该点到目的地的距离那么就转移。
CDOJ1367
类似树状数组的方法,记录dp[i][j]表示从1,1到i,j的元素的和,那么有:
dp[i][j]+dp[i-1][j-1]-dp[i-1][j]-dp[i][j-1]=f[i][j],其中f[i][j]表示i,j这点是否有东西。
移项得:dp[i][j]=f[i][j]+dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1],然后递推即可。
对于每个查询,我们枚举全部的左上角,看一个区域A,B内元素和是否为0即可,元素和依然用树状数组的类似方法计算。
HDU1505 POJ1964
类似HDU1506的题目,首先先预处理出每个格子向上可以达到的最大高度,这个可以用dp实现,如果这个格子是R那么dp[i][j]=0,否则dp[i][j]=dp[i-1]j]。
然后枚举每一行,把这一行当做最下方的一行,对这个模型运用类似HDU1506的压缩路径的方式在O(n)的时间内处理。总的复杂度是O(n^2)的。
HDU1422
单调队列优化的dp。首先先定义dp[i]表示以i结束的旅程的最大长度,如果第i个城市的生活费加上之前剩下的生活费之和大于在第i个城市的花费时,显然有dp[i]=dp[i-1]+1
现在利用单调队列,维护一个宽度为n的窗口,记录花费前k项和在一段区间的最小值,再记录一个f指针表示我们的起点,当到达i这个城市的时候钱不够时,也就是说这个区间的最小值与前f项和的差值小于0的时候,我们把f自增。这样可以在O(n)的时间内完成dp的转移。
CDOJ1214
二分加dp,首先二分答案,然后每次用dp判断是否可行。
设dp[i]表示前i个元素符合每一堆的元素和不超过mid的条件下,需要多少堆。容易想到转移dp[i]=max{dp[k]}+1,其中k
POJ1015
这题dp主要是定义状态,如果定义错了的话很可能不满足无后效性。
错误的定义方式:定义p[i][j]表示考虑到前i个元素,已经选择j个的,达到最优解时的p值之和。d[i][j]也如此定义。用pre[i][j]记录路径。以上的方法的错误的。这样定义状态不满足无后效性,完全有可能后面的解是从前面的非最优解转移过来。
正确的方法是记录dp[i][j]表示已经选择了i个元素,p和d的差值为j的时候p与d之和的最大值,另外加两个辅助数组,pre[i][j]表示取得dp[i][j]的时候最后取得的元素id,s[i]表示第i个元素的p值与d值的差。
状态转移方程(从前向后推):dp[i+1][j+s[k]]=max(dp[i+1][j+s[k]],dp[i][j]+p[k]+q[k]),如果可以转移,那么就更新pre[i+1][j+s[k]]=k。
注意转移的之前要判断路径上是否已经有id为k这个元素,如果有,就不能转移,所以要写一个check函数判断。
最后根据pre数组得到路径,假设最优解是st,那么路径上最后一个元素就是pre[m][st],之前一个元素就是pre[m-1][st-s[pre[m][st]]],以此类推。在记录路径的同时对各个元素的p值和d值求和。
一般遇到差值最小、差值的绝对值最小这类问题,我们可以直接把差值当做一维状态来dp,注意要设置offset参数,防止数组越界。
此外还要注意这道题我们dp的顺序,只有把m放在for循环的第一维,我们才能保证我们的路径不会被覆盖。
POJ1141
经典dp,括号匹配问题,直接定义dp[i][j]表示从i到j这个范围最少需要添加多少括号。转移分四类:
第一类是s[i]和s[j]能配对上,也就是说s[ij]=()或者[]的时候,dp[i][j]=min(dp[i][j],dp[i+1][j-1])
第二类是s[i]是左括号的时候,我们可以在最右边加括号匹配,dp[i][j]=min(dp[i][j],dp[i+1][j]+1)
第三类和第二类对称,s[j]是右括号的时候,dp[i][j]=min(dp[i][j],dp[i][j-1]+1)
最后考虑将序列分成两个部分,枚举得到dp[i][j]=min{dp[i][k]+dp[k+1][j]}
利用一个数组ans来记录这个最优解的来源,负数表示前面三种情况,整数表示最后一种情况的分界点。
这题利用记忆化搜索会很方便。时间复杂度是O(n^3)的。
HDU1423 CDOJ1300
LCIS,经典问题,记录dp[i][j]表示前i个A的元素和前j个B的元素构成的LCIS长度,限制以Ai结尾,而B不做限制。
转移方程dp[i][j]=max{dp[k][j-1]}+1,k
HDU1300
先理清题意,本题的意思是低等级的珍珠可以用高等级的代替,但是必须全部代替,这样我们就可以直接寻求最优策略。
设dp[i]表示前i个物品需要的花费,那么显然有dp[i]=min{dp[k]+cost[k+1][i]},注意这里的cost[k+1][i]的计算方法即可。
HDU1502
定义dp[i][j][k]表示A有i个,B有j个,C有k个的时候的方案数,那么对于i
否则dp[i][j][k]=dp[i-1][j][k]+dp[i][j-1][k]+dp[i][j][k-1]。
由于会超long long所以用Java写大数。
HDU3530
维护两个单调队列,一个维护最小值,一个维护最大值,每次只要判断最大值减去最小值的值是否大于k,如果大于k那么就移动f指针,否则不移动,注意记录一个t变量表示起点,初始化t为-1。在处理完队列之后我们查看最大值减去最小值是否大于m,如果大于m就统计长度,否则不更新。
HDU1244
记录dp[i][j]表示以第i个元素结尾,有j组的时候的最大和,那么有dp[i][j]=max{dp[k][j-1]}+sum[i-l[j]+1][i],其中k+l[j]<=i,那么这样的转移是O(n)的,为了优化,记录s[i][j]表示s[0][j]到s[i][j]中的最大dp值,那么可以在O(1)的时间内做到转移,最后可以得到一个转移方程:
dp[i][j]=s[i][j-1]+sum[i-l[j]+][i]。sum数组可以用前n项和得到。
HDU1243
LCS的变形,直接记录数组sc表示用某个字母击中对手得到的积分,那么当a[i]=b[j]的时候,dp[i][j]=dp[i-1][j-1]+sc[a[i]-‘a’]。其余转移和LCS一样。
HDU2870
最大完全子矩阵,枚举a、b、c然后运用类似HDU1506的方法来求出最优解。
CF47-D
首先二分半径,看至少有k个被摧毁的概率是否大于1-e,如果大于,就减小半径,否则增加半径,至少k个建筑被摧毁的概率计算方法如下:
记录dp[i][j]表示前i个有j个被摧毁的概率,那么有dp[i][j]=dp[i-1][j-1]*p[i]+dp[i-1][j]*(1-p[i])。
POJ1037
组合dp,考虑如下的问题:
在一个n个数的全排列中找到第k个排列,我们的做法是从前向后扫,比如我们要找到4个数的第10个排列,首先我们尝试第一个数,如果第一个数是1,那么剩下的3个数的排列有3!=6种,然而6<10,这说明了第一个数比1大,我们尝试2,剩下的三个数的排列依然是6个,这时候我们想,2134这个排列应该是排在第1+6=7位,这说明,我们如果以2开头,我们要找的是剩下的第10-6=4位排列,而三个数的全排列是6,比4来的大,所以第一位就是2,我们要做的就是找到由1、3、4三个组成的第4个排列,这样我们就可以用一个dfs来递推执行。
这道题目难度加大了一些,考虑的是“波浪形“的序列,那么我们利用dp记录dp[i][j][0]和dp[i][j][1]表示长度为i的以j开头的序列,并且这个数比上一个数来的大(或者小)的时候,可能的方法数,这时候我们可以写出转移方程:
dp[i][j][0]=Σdp[i-1][k][1]其中1<=k
dp[i][j][1]=Σdp[i-1][k][0]其中j<=k
这样我们考虑到一个数的时候我们就直接通过累计的方式计算出这一位是是剩余的数字中的第几大。
注意一下就是统计第一位的时候有可能有两种情况,一种是第一个数大于第二个数,另外一种是第一个数小于第二个数,注意一下前面一种情况的排列一定是小于后面一种情况,所以求和的时候先加上dp[n][i][0],如果不成立则加上dp[n][i][1],以此类推。
POJ3624
裸的0-1背包,直接做。
POJ3628
利用bool型标记每个高度是否可达,最后枚举即可。
POJ2184
记录dp[i]表示s的值是i的时候最大的f值,最后直接统计就可以了,这题注意offset的利用,还有就是不能直接使用这个数组,上界是2000n,offset是1000n,如果使用整个数组,会TLE。注意当s的值是正的和负的时候for循环的方向不一样。
POJ2264
LCS构造解,开一个数组s[i][j]表示取dp[i][j]的时候的方向,然后构造出LCS,之后按照a串优先的方式打印出最后的结果。
POJ2955
典型的O(n^3)的复杂度dp。定义dp[i][j]表示区间[i,j]中的最大长度,那么如果s[i]=s[j]那么dp[i][j]可以由dp[i+1][j-1]+2转移,如果s[i]是左括号那么dp[i][j]可以由dp[i+1][j]转移,如果s[j]是右括号,那么dp[i][j]可以由dp[i][j-1]转移。最后dp[i][j]可以由dp[i][k]+dp[k+1][j]转移。
POJ1745
dp,定义dp[i][j]表示前i个数是否可以组成模k等于j的结果,直接转移就可以。
最后判断dp[n-1][0]是否是true即可。
POJ3494
最大子矩阵,直接做即可,和city game一样的做法。
POJ2033
定义dp[i]表示以第i个数结尾的结果数目,直接转移就可以了,注意如果相邻的两个数都是0,那么答案是0,如果开头的数是0,答案也是0。
转移的时候按照s[i]是不是0分开考虑,注意数组大小,开1200就WA了,开120000过的。
POJ2704
记忆化搜索,直接定义dp[i][j]表示到达格子(i,j)的方法数,通过同一行靠左边或者同一列靠上边的格子转移。
POJ2696
直接记忆化搜索即可,注意一下dp[i]的值是非负的,如果小于0,就要加一个模数。
POJ2342
经典树形dp,直接定义dp[i][0]表示第i个结点为根,并且第i个结点不是参加人员的时候获得的最大rating,定义dp[i][1]表示第i个结点为根,并且第i个结点是参加人员的时候获得的最大rating,那么显而易见dp[i][0]=Σmax(dp[k][0],dp[k][1]),dp[i][1]=Σdp[k][0],其中k是i的孩子结点。
使用记忆化搜索,注意dp数组的初始化。
NOI2005瑰丽华尔兹
单调队列优化的dp,记录dp[k][i][j]表示在第k这阶段在(i,j)这个位置的时候滑过的最长距离,那么以向下为例,那么有dp[k][i][j]=max{dp[k][i'][j]+i-i'},这里的枚举一维可以用单调队列优化。
这题有几点比较重要的地方,首先是最大值函数,不是dp值,而是dp值与一个关于i或者j的变量的和,我们维护队列的时候要维护的也是这个值,而不是dp值。此外,在每次枚举一维之前要初始化队列,注意是在枚举的那一维之前,而不是全局枚举之前。
NOI练习 生产产品
单调队列dp,记录dp[i][j]表示第i个生产线生产到第j个阶段的时候花费的最小时间,那么j从1开始i计数,定义dp[i][0]=-K。转移方程dp[i][j]=min{dp[k][j']+t[i][j]-t[i][j']+K},其中j-l<=j'<=j-1,k<>i。注意这里由于是j'<=j-1,所以新的dp值不能优先入队。
注意t[i][j]表示第i个生产线生产前j个阶段花费的时间,这里可以用单调队列优化,不过优化方法非常的特殊,我们先枚举j,再枚举i和k,对每一对i和k都开一个单调队列,然后优化即可,注意f和b指针的维护。
HDU3401
单调队列优化dp,定义dp[i][j]表示第i天手上有j份股票的时候的赚到的钱的数量,那么可以到达dp[i][j]的路径有dp[i-1][j]和dp[k][j'],其中0<=kj,那么j'-j<=bs[i]。
那么以买入为例,dp[i][j]=max{dp[k][j']-(j-j')*ap[i]}。
注意这里,由于dp[i][j]可以由dp[i-1][j]转移过来,所以显而易见有dp[i][j]>=dp[i-1][j],所以对于第一维,我们可以优化成k=i-w-1。对于第二维,利用单调队列就可以达到效果。
HDU2993
斜率优化入门题,注意一下这题数据可能过多,加了外挂才过的。
方法是维护一个下凸的图形,首先构造函数
f(i,j)=(s[i]-s[j-1])/(i-(j-1))
然后根据函数构造一系列的点P(i,s[i]),然后维护斜率队列,注意一点,判断凸性用叉积判断,队列的b指针维护凸性,f指针维护一个最优值。
用一个图可以很清楚的看出来,如果f+1所在的点对应的斜率比f对应的大的话,f就对以后的全部点都没有作用了,所以我们可以进行f++操作。这样最多一进一出,可以在O(n)的时间内得到结果。
这里为了方便,我使用的是点队列。
POJ3017
经典dp,利用单调队列加平衡树。
首先定义状态,定义dp[i]表示以i结尾的最小值,那么有转移方程dp[i]=min{dp[k]+max{a[k+1,…,i]}},其中sum[k+1,…,i]<=m。
注意k的范围,k的上界是i-1,下界可以利用一个指针lim来向后移动,注意每个元素都是正数,那么下界一定是单调递增的。
接下来我们要处理k的值,注意满足条件的k一定是a[k]>=max{a[k+1,…,i]}的,这样我们维护一个单调递减的队列,维护的标准是对应的a值单调递减,队列中还要维护一个pos和一个val,val是函数值,我们令val=dp[q[x].pos]+a[i],注意这里是a[i]是因为max{a[k+1,…,i]}一定是a[i],这里正是利用了单调队列的性质来维护的。
注意队列中a值单调的,但是val值并非是单调的,那么我们要维护一个平衡树来维护val的值,然后每次取一个最小值作为dp值的解。
平衡树中比较好写的就是sbt了,利用sbt可以比较快的完成这个任务。
最后要注意一下,我们先将pos=1的元素先入队一次,这样的话如果队列只有一个元素的时候,由于没有dp[q[x].pos]+a[q[x+1].pos]这种写法,那么这时候我们就不管这个,因为这时候sbt中一定是没有元素的。
NOI练习 锯木厂选址
斜率优化dp,首先我们先做如下的定义:
sumd[i]表示第i颗树到第1颗树的距离
sumw[i]表示前i颗数的总重量
c[i]表示在第i个位置建立第一个锯木厂,将前i颗树送到这个锯木厂花费的钱
w[i,j]表示在第i个位置建立第二个锯木厂,将j+1颗树到第j颗树都送到这个锯木厂花费的钱
dp[i]表示在第i个位置建立第二个锯木厂,花费的最低总价钱
明显有
sumd[i]=Σd[k],1<=k<=i-1
sumw[i]=Σw[k],1<=k<=i
c[i]=c[i-1]+sumw[i-1]*d[i-1]
w[i,j]=c[i]-c[j]-sumw[j]*(sumd[i]-sumd[j])
dp[i]=min{c[k]+w[i,k]+w[n+1,i]}
注意我们定义n+1位置有一颗树,d[n+1]=0并且w[n+1]=0。
我们定义t[k,i]=c[k]+w[i,k]+w[n+1,i]
根据单调性定义我们对k1
NOI练习 玩具装箱
斜率优化dp,我们定义dp[i]表示前i个玩具打包需要的最少价值,定义s[i]=Σc[k]+i 1<=k<=i,其中s[0]=0,那么明显有递推式s[i]=s[i-1]+c[i]+1。
接下来我们可以写出dp的转移方程dp[i]=min{dp[k]+(s[i]-(s[k]+1)-L)^2},利用单调性直接找到对应的斜率就可以了。
注意dp方程不要写错了,减去的是s[k]+1而不是s[k]。
HDU3507
斜率优化dp,定义s[i]表示前i个元素的c[i]之和, dp[i]表示前i个元素打印需要的花费,那么有dp[i]=min{dp[k]+(s[i]-s[k])^2}+M。
以上的式子满足决策单调,直接利用斜率优化来处理,可以在O(n)的时间内解决。
HDU2829
斜率优化dp,定义dp[i][j]表示使用了j个炸弹,前i个元素的最小值,cost[i][j]表示从i到j这些元素的乘积和,那么有
cost[1][i]=cost[1][k]+cost[k+1][i]+Σc[j]*(s[i]-s[k])=cost[1][k]+cost[k+1][i]+s[k]*(s[i]-s[k])
所以cost[k+1][i]=-cost[1][k]+cost[1][i]+sqr(s[k])-s[k]*s[i]
dp转移方程dp[i][j]=min{dp[k][j-1]+cost[k+1][i]}其中cost的公式已经给出。
最后我们只要维护一个c[i]表示cost[1][i]即可,这时候我们换一个公式c[i]=c[i-1]+s[i-1]*val[i]。
剩下的问题就利用m个单调队列就可以搞定。
这题也可以用用四边形不等式在O(n^2)的时间内转移。四边形不等式就定义dp[i][j]表示有i个炸弹前j个元素的最小值,直接转移即可。
HDU1561
树形dp,定义dp[i][j]表示选择以i为根的结点,有j个课程被选择到的时候得到的最大学分,那么有dp[i][j]=dp[k1][k]+dp[k2][j-k-1]+val[i]。
注意k1和k2表示二叉树的两个孩子结点,val表示第i个课程的学分。
最后的答案是dp[0][m+1],注意由于选择了第0个课程,所以要多加一个1。
这题运用到了多叉树转化二叉树的方法,记录二叉树的左孩子是多叉树的孩子结点,右孩子是多叉树结点的兄弟,记录last变量表示该结点的最后一个孩子。那么每次连接边(u,v)的时候我们查看last值是否是-1,如果是,那么son和last都为v,否则设置last的bro为v,u的last为v。
ZOJ3463
定义dp[i][j][k]表示到第i个音符,左手在j,右手在k的时候花费的最小能量,注意双手不能重叠并且有有一只手可以碰到第i个音符。否则dp[i][j][k]=-1。
这题的意思是大拇指到小拇指方向的9个按键都可以按到,理解了好久,o(╯□╰)o。。
之后取dp[n-1]的最小值就可以了。