杭电 25道动态规划汇总

HDU 25道动态规划题的解题报告

http://acm.hdu.edu.cn/webcontest/contest_show.php?cid=1264

密码:zafu

A.         最简单的dp,但很重要。说明了动态规划产生的动机之一:递归产生的大量重复子问题。有两种解决方法,一、记忆化搜索,就是在用递归处理问题的过程中把已经算过的状态记录在一张表dp[][]中,下一次需要重复计算时直接返回。二、自底向上的递推,可以理解为递推的逆过程,就是从最小的子问题去推导更大的子问题。

dp[i][j]表示由该点i,j出发往下走的最大价值

这题的状态转移方程显然是

dp[i][j]=max(dp[i+1][j],dp[i+1][j+1]);(最后一层直接返回该数)

B.         dp经典问题LCS。还是思考如何把问题的规模缩小,那么我们首先取两个串各自的第一个或最后一个进行比较,可以进行最优操作(为什么,请思考)。

情况一、两者相等,则取之作为最长公共子串,问题就转化为比较len1-1 、len2-1的最长公共子即可。

情况二、两者不相等。则必然舍弃某一个的其中一个字符,再进行接下的比较。问题就转化为len1与len2-1比较,或者是len1-1与len2比较,取大者。

dp[i][j]表示str1的前i个与str2的前j个的最长公共子串。

状态转移方程、

   If(str1[i-1]==str2[j-1])

       Dp[i][j]=dp[i][j]+1;

   Else

       Dp[i][j]=max(dp[i][j-1],dp[i-1][j]);

C.         Dp经典问题,LIS,最长上升子序列。首先介绍O(n^2)的方法,对第i个数考虑,以找其前一个数为突破口,如果i的前一个数为j,那么问题就从找前i个数的LIS转化成找前j个数的LIS。

Dp[i]表示前i个数的LIS数值 ,那么,

Dp[i]=min{dp[j] | (a[j]<a[i])&&1=<j<i}+1

不幸的是O(n^2)是处理不了这题的,于是思考,应该如何进行优化,考虑到问题的稀疏性,即有些子问题对后续的问题是没有帮助的,那么我们就不需要保留它的状态。

如果dp[i]>=d[j]&&d[i]<a[j]的话,那么后面的数再进行决策时决不会选j作为它的前一个数,所以j不需要保留。因此需要保留的只是,记为f[i]表示此前最长上升子序列为i的序列最后一个数的最小值。那么显然f[i]是一个递增学列,我们可以用二分法对其进行更新,那么更新到n时这个数组的长度,就是答案(请思考)。

D.         还是和最长上升子序列类似,而这题只需要用O(N^2)的复杂度就可以解决。

Dp[i]表示前i个数的最大递增和。

状态转移方程、

Dp[i]=max(dp[j]+a[i]   |  (a[i]>a[j])&&1=<j<i)

E.         求最大连续子段和。先思考对第i个数,我们要根据什么进行决策,是以第i-1个数为结尾的最大连续子段和,那么,dp[i]的状态可以描述为以第i个数为结尾最大连续子段和。显然,对于i有两种决策,要么和前i-1个数连续,

要么自己成为一个新的段。

      状态转移方程为、

      Dp[i]=max(dp[i-1]+a[i],a[i]);

      由状态转移方程可以发现,其实和前面连续还是取自身只要判dp[i-1]是否大于0就可以,至于起始位置和终止位置,自己去模拟。

F.          求最大矩阵和。很暴力的方法就是取出每个矩阵,得出最大值,显然是会超时的。那么,我们能不能利用05的结论做一些变化?如果a[j]表示前i行第j列的和的话,我们对a数组求最大连续子段和,其结果其实就是矩阵成都为i行里的最大矩阵和(好好理解)。那么,我们只要取一个起始(1-n),取矩阵的长度(1-n)求最大连续子段和O(n),也就是说用O(n^3)的复杂度就可以处理这个问题。

G.         6-18道就都是背包问题,大家好好理解背包九讲,里面说的很清楚,那么,对于接下来的题目,如果是只是照搬背包九讲的结论,我就不详细解释了,对于有特点的题,还是会给出思路,关于初始化的问题,背包九讲里也有,我就不赘述了。     这题是赤果果的01背包题。那么一维的状态转移就是、dp[i]=max(dp[i],dp[i-c[i]]+v[i])  逆序遍历

H.         这题是赤果果完全背包,注意初始化。那么一维的状态转移就是dp[i]=max(dp[i],dp[i-c[i]]+v[i])和01背包一样,但要顺序遍历背包容量。

I.                 这题是赤果果多重背包,好好学习多重背包的转化01背包的二进制数方法。我知道大部分人是直接转化成n件物品的01背包做的。但如果数据量增大的话,就处理不了了,后一种方式在复杂度上有很大优化。

J.          这题比较灵活,我详细说一下。很自然,我们会想到以概率作为背包容量,但以小数做为背包容量显然是不现实的,就算扩大它的倍数转化为整数,但小数一旦很精确的话,也是无法表示的。所以,我们换一种方式思考,把钱作为背包容量。还有一个问题就是题目给出的是去抢一个银行被抓到的概率,那如果抢了两个银行被抓到的概率又该怎么算?这里,我们可以逆向思考,抢一个银行不被抓到的概率就是1-a,所以抢两个银行不被抓到的概率就是(1-a)*(1-b)。那么我们可以用dp[i]表示抢到iW钱,不被抓的概率。只要这个概率大于(1-P)就可以了,注意背包要装满,我们要找的就是背包装满的情况下,dp[i]>=(1-P)的最大的i。

K.         题意是由多件物品组成最相近的两个数,且物品个数一定。设物品的价值为W,有两种方法,一种是背包可以不装满,那么dp[W/2]和W-dp[W/2]就是答案。 另一种是将背包装满,只需要从W/2处开始遍历找到的第一个大于0的数就是答案。

L.          很简单的方法是,因为物品可以无限取,那么在背包容量一定的前提下,求费用最小的物品就是答案。还有一种方法就是直接做完全背包,物品价值为1。

M.        这题也是赤果果的二维费用背包,不理解的再去看背包九讲,注意初始化,即考虑状态的意义,没有意义的状态使之不影响后面的状态。可以在装满的背包中逆序搜经验值大于升级所需,第一个搜到的就是答案。也可以在不装满的背包中搜整张表,经验值大于升级所需,并且所消耗体力最小。

N.         这题是比较详细的背包问法变化问题,求第K大背包。显然dp[i]记录的状态已经无法满足题目的需要,我们还是要从无后效性(即这个仅根据哪些特征决策)考虑如何描述这个问题,显然,对于每个背包容量都要保存前K大的值,这样才能进行状态转移。那么,可以用dp[v][k]表示背包容量为i的第k大价值。显然dp[v][1…k]这K个数是由大到小排列的,所以是一个有序序列。那么有这里有两个有序序列f[i-c[i]][1…k]+v[i]和f[i][1…k],合并这两个有序序列并将结果(的前K项)储存到f[i][1…k]上,最后的答案就是f[v][k].

O.         这题是每组至少取一件的的背包。而分组背包的含义是每组最多取一件,我希望大家在做这题的时候,思路不要被分组背包限制住,虽然和分组背包最大的区别只是把物品遍历的顺序放到第二层上。而用常规dp状态转移的方式去考虑这个问题。   还是一样,先从无后效性去考虑描述问题的方式。这个问题显然是对于第i组物品的状态既可以从第i-1组来也可以从第i组来,而分组背包是状态只从i-1组来。(请仔细思考二者的区别)所以需要记录组别来进行状态转移。  于是,我们可以用dp[i][j]来表示取到前i组物品,背包容量为j所能获得的最大价值。

状态转移方程、

dp[i][t]=MAX(dp[i][t-s[i].c[j]]+s[i].v[j],dp[i-1][t-s[i].c[j]]+s[i].v[j],dp[i][t]);//至少取一件,所以只能去,要么从第i组来,要么从第i-1组来。

 

P.         这题和M几乎是一样的,就是赋初值的时候要稍微注意点。(自己思考,就不再赘述了)

Q.         这题是赤果果的分组背包。详见背包九讲。三层循环。组别、容量、该组物品。(重在理解)

R.         这题是所选的背包题中最难的一道,也是本背包关的小BOSS,这题如果在独立思考的前提下,完全弄懂各种背包的联系区别,并不被其所限,你就可以去拯救世界了。这题的物品组有三种情况,0表示的是O的至少取一件的背包。1表示的是Q的常规分组背包,2表示的其实就是对该组物品做01背包。状态描述显然和O一样,用dp[i][j]来表示取到前i组物品,背包容量为j所能获得的最大价值。

状态转移方程是:

state=0      dp[i][k]=max(dp[i-1][k-job[i].c[j]]+job[i].v[j],dp[i][k-job[i].c[j]]+job[i].v[j],dp[i][k])

State==1  Dp[i][k]=max(dp[i-1][k-job[i].c[j]]+job[i].v[j],dp[i][k-job[i].c[j]]+job[i].v[j],dp[i][k],dp[i-1][k]);

State==2

Dp[i][j]=max(dp[i][k],dp[i-1][j],dp[i-1][j-job[i].c[k]]+job[i].v[k]);

  看着有点烦,但请仔细思考。

 

S.接下来的问题就是常规的dp题了,而我们之所以讲了那么多的经典模型,就是为了更熟练地去处理这些常规的问题。

这题的思路是先考虑出i-j段被一个邮局覆盖的最短距离和才能进行状态转移,对于这个问题,显然是建在i-j的终点所在的村庄上是最优的,就先不做证明了。这样我们就可以先预处理出任意i-j段被一个邮局覆盖的最短距离和用map[i][j]表示。那么,dp[i][j]状态的描述是前i个村庄被j个邮局覆盖的最短距离和。

状态转移方程显然是

dp[i][j]=min(dp[i][j],dp[i-1][j-k]+map[j-k+1][j])(1=<k<=j)

 

T.这题与最长公共子序列有点类似。都是比较两个串各自的最后一个字符,首先要说明的是向一个串中插入一个字符和向另一个串中删除一个字符其实是一样的,为了便于理解就舍掉插入的操作。显然,状态的描述和最长公共子序列类似,dp[i][j]表示的是str1的前i个值转化为str2的前j个值。

If(str1[i-1]==str2[j-1])

   那么要么转化成为前i-1个和前j-1个转化,或者删除其中一种

If(str1[i-1]!=str2[j-1])

   那么要么change,要么删除其中一种

于是状态转移方程:

if(str1[i-1]==str2[j-1])                         dp[i][j]=min(dp[i-1][j-1],dp[i][j-1]+1,dp[i-1][j]+1);                 

else                            dp[i][j]=min(dp[i-1][j-1]+1,dp[i][j-1]+1,dp[i-1][j]+1);

 

U.这题比较难考虑,因为有至少要学L分钟这个限制。很自然我们想到状态描述显然是dp[i][j]表示前i分钟已经睡了j分钟。状态转移方程也很容易推:

   两种决策,要么睡,要么学习,而学习至少要学L分钟,那么dp[i][j]=max{dp[i-1][j-1],dp[i-t][j] | L=<t<=i}  得出状态转移方程之后,对于每个i,j都要有个遍历t的for,复杂度为O(n^3 | n= 1000),显然需要优化。而对于dp[i-t][j]我们可以记录max_j[]记录第j行1到i-L的最大值,一直更新这个最大值。

   if(i-l>=j)               max_j[j]=max(dp[i-l][j]+sum[i]-sum[i-l],max_j[j]+v[i])

 

V.     这题其实就是求公共最长上升序列,只不过把字符变成字符串。现在的问题是求出了dp[][]之后,如何输出其中一个方案?  往回找路径就可以了。有困难的话可以参考以下代码,最好还是独立思考自己完成 - - 。

     while(len1>0&&len2>0)

      {

if(dp[len1][len2]==dp[len1-1][len2-1]+1&&strcmp(str1[len1],str2[len2])==0)

{                           strcpy(jilu[num++],str1[len1]);

                              len1--;

                              len2--;

           }

           else if(dp[len1][len2]==dp[len1-1][len2])

                          len1--;

           else

                          len2--;

    }

 

W.这题的难点在于初始化,状态的描述(怎么去思考我就不赘述了,请回忆上课讲过的东西)dp[i][j]表示str1的前i个和str2的前j个能否构成str的前i+j个,如果可以,赋值为1 否则为0.

状态转移方程、

If((str1[i-1]==str[i+j-1]&&dp[i-1][j]==1)||(str2[j-1]==str[i+j-1]&&dp[i][j-1]==1))

     Dp[i][j]=1

Else

Dp[i][j]=0;

这题的初始化是最重要的地方,请根据以上状态转移方程和状态的实际意义仔细考虑下。

 

X.状态的描述总是那么明显,dp[i][j]表示前i个数E-value恰好等于j的个数。那么,我们考虑添加一个数i,如果放在最后或者是和原来就是E-value的位置交换,那么E-value的个数肯定不边,(思考为什么),如果和原来不是E-value的数进行交换,那么个数-1(思考)。SOGA,这样一来状态转移方程就非常清晰了。

 dp[i][j]=((dp[i-1][j-1]*(i-j))%mod+(dp[i-1][j]*(j+1)%mod))%mod

 

W.这题有点复杂,涉及的只是也有点多,来年的集训的最后一课,我打算当作例题给大家分析下,所以就先不写出解题报告了,感兴趣的同学可以先思考,如果有哪位大神可以直接做出,我也表示衷心的YM。

That is all。希望大家好好思考动态规划,学好大学阶段最重要也是最精致的算法,请用力。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值