DP Dynamic Programming Study Notes

As the most extensive study to investigate the highest number of algorithms, of course, to open a blog to review it.

Confucius said: Reviewing the Old, can serve as a teacher

DP when I review some of their own understanding of the DP, also share out of it.

- positive start -

Dynamic programming algorithm, namely Dynamic Programming (hereinafter referred to as DP), is a highly efficient mathematical methods to solve the multi-stage decision-making process optimization problems. Since the title in 1999 IOI called a "digital triangle" of, DP title on widely circulated in OI competition. The above-mentioned "digital triangle", now is an entry-DP title.

DP of recurrence and relationships:

Many people confuse recursion and DP, just a recursive implementation of DP. DP we mentioned is an efficient algorithm to achieve mainly through recursive search and memory, but much like the DP and recursive point is, they are to get the original problem using the sub-problems.

Let me give you an example, Fibonacci number, I believe everyone is familiar with.

We know Fib recurrence formula: F (x) = F (x-1) + F (x-2), by the first term and the second x-1 x 2-Item Release item x.

If we perspective view of the DP Fib, we can find the original problem as item x, then we find the first term x-1 and x-2 requirements of items as sub-problems, we put "find item x "the original problem is divided into" seeking first x-1 item "and" seeking first x-2 of "two sub-problems, and then continue to divide and sub-problems to solve, and finally take advantage of all the sub-problem solution of the original problem.

Many DP blog entry will carefully explain the three basic features of nature, problem-solving steps and use of DP DP.

Do not understand these, I picked up from @mengmengdastyle's blog to show you in this section:

(2) dynamic programming comprises three important concepts:
- optimal substructure
- Boundary
- state transition equation
(3) is solving the general steps:
1. Find the optimal solution properties, which characterize the structural features and the optimal wherein substructure;
2. recursively defined optimal value, depicts the relationship between the solution and the solution of the original problem of sub-problem;
3. a bottom-up fashion calculate each sub-problem, the optimal value of the original problem, and to avoid subproblem double counting;
obtained 4. the optimum value calculation information, construct the optimal solution.
(4) using the dynamic programming characteristics:
1. a seeking optimal solutions
2. big problem can be broken down into sub-problems, as well as overlapping subproblems of subproblems smaller
3. The optimal solution depends on the overall subproblems optimal solution (the state transition equation)
4. from the analysis of the problem down, from the bottom up to solve the problem
the problem underlying the discussion boundary

the above.

This article focus on ideas and problem-solving skills, basic knowledge for much repeat.

Let's analyze a question, make you understand this question by DP are used to doing.

Give you a height of x (1 <= x <= 2x10 ^ 5) staircase, each level can go up 1 or Level 2, you went to the top and asked a total of how many moves.

Sample input:

10
sample output:

89

Let's analyze the problem, if x = 1, the answer you can easily get, equal to 1 if x = 2 it? The answer is 2, and very easy to get.

But our x is great, we can not calculate the results are put aside for each x hand.

So we consider the problem of the decomposition, the original problem is decomposed into several sub-problems, then for each sub-problem decomposition down.

"Major issues to minor ones out."

Then how do we break down this problem? If we take the number of moves required level of 3, first of all see if we can go with Level 1 and Level 2 program number to go home to scrape together the number of program-level 3. Thus, we find, indeed. We walked a few moves Level 3 is to take the number of moves + 1 level of class walking the walk number 2.

Solutions to this problem too: the number x down stage moves away Number = x-1 + stage moves down the number of moves in x-2 level.

This question is seeking a Fibonacci number, we use recursive get an answer.

Introduce a more difficult problem at present, I'm going to talk about the DP itself.

OI in the competition, we use the computer to calculate the answer. But we can only use the computer to record some of the state, we need to use to solve the state's record, so we want to try to define the problem as a state. A lot of people talking about DP's time, saying the need "recursive" definition of state. For this argument, we generally use two words to describe: nonsense. We really need to define the state, but every problem can be defined as a state, we have to do first of all is to think about how to define it, in general, is to think about what variables we keep, what variables considered, how come these variables solution.

We generally divide the problem into different stages, each stage has a different status. However, we do not need to calculate all states survive, every step we just need to keep the best answer on the line, this is the DP reasons for seeking the optimal solution. Since problems can be divided into stages and state, then the optimal solution a certain stage it will be able to get through the stage before the optimal solution.

But if we answer only via a pre-stage of the current phase of the answers could not count it? If we need to answer in front of all phases of it?

If at each stage of the problem, a state can be transferred to the next stage of a plurality of states, we calculate the time that the solution is the index level of complexity, that we do not use DP to solve this problem. This, in front of the decision will affect the latter case, it is called there after-effect.

DP remember one of the three elements of it? No after-effect.

Remember the entry title search of it? 01 maze.

If we want to find the shortest path from the beginning to the end, we can only save the state of the current phase of it? Obviously not. Think of the time do we save time status? {X, y, step}, and a vis array. Since the subject of the request that we seek the shortest path, we must know the location of all before traveled.

Even if we are currently in the same position, before we take a different route, but also will affect the path we have chosen to go back in, because we will not have to go over the mark vis the grid .

So we have to save all the status of each stage experienced solution to get the next stage.

This is the aftereffect is an example of the problem.

如果我们需要记录之前所有的状态,我们的复杂度就是指数级的,但是DP呢?

我们并不需要记录之前的所有状态,我们当前的决策并不受之前状态的组合的影响了,就可以多项式时间内出答案了。

引用一段@X丶dalao的blog:

每个阶段只有一个状态->递推;
每个阶段的最优状态都是由上一个阶段的最优状态得到的->贪心;
每个阶段的最优状态是由之前所有阶段的状态的组合得到的->搜索;
每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到而不管之前这个状态是如何得到的->动态规划。

每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到
这个性质叫做最优子结构;

而不管之前这个状态是如何得到的
这个性质叫做无后效性。

好了,现在我们讲题。

网上各种什么,让你彻底学懂DP啊,特别的DP入门教程啊,其实都不如自己多写点DP题来的实在...

下面我将从几道例题开始,从易到难慢慢打开DP的大门。

1. 石子合并:
有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。

首先我们来划分阶段,我们有一坨长度为n的石子堆,我们每次合并后,石子堆的数量都会减少,那我们就从这里切入。

直观地想,我们可能会这样划分阶段:

我们要合并石子,肯定就要找一个地方,把它两边的石子合并起来。              

设f[x]表示合并了x次的最小总代价。立刻就能发现不对...我们选定不同的地方来合并,每次的答案时不同的,也就是说f[x]的值不定,这时肯定是得不到最优解的。有人可能会有疑问,f[x]不是定义成了最小定义的代价了吗?

那你回去仔细看看上面说的关于状态定义的内容。

所以我们需要重新定义状态,这里给出一种划分方法,我们用f[i,j]表示合并区间左端点为i,右端点为j的这段区间合并成一堆石子的最优值。

为什么这么定义呢?这就涉及到一类问题:区间DP

对于区间DP,我们利用区间长度作为阶段,用左右端点表示状态。这种定义方法可以解决大部分的区间DP问题了,但是遇到一些难题,我们还需要加维度来解决。

我们上面提到过,要合并两堆石子,我们就要循环一个分界点,我们定义一个分界点k,枚举这个分界点找最优解。这个过程我们称之为——决策!

然后我们利用决策转移状态(用子问题求解出原问题):

下面这个式子就是我们常听到的“状态转移方程”:

f[i,j] = f[i][k] + f[k+1][j] + cost(i,j),其中cost(i,j)表示合并两堆石子的代价。

然后我们思考一下状态的可选范围,i表示左端点,j表示右端点。

i: 1~n-len+1,j:i+len-1,这样我们就保证了既不超出边界,又能保证我们的阶段是区间长度len。

阶段就是len:2~n

这种做法时间复杂度是O(n^4),我们发现没法简化定义了,于是我们O(n^3)预处理出cost(i,j),再O(n^3)DP得出答案。

伪代码大概是这样的:

for(i,1,n)
    for(j,i,n)
        for(k,i,j)
            cost(i,j)+=w[k]
for(len,2,n)
    for(i,1,n-len+1)
        j=i+len-1
        for(k,i,j)
            f[i][j]=min(f[i][j],f[i][k]+f[k+1][r]+cost(i,j))

如果还是不太理解的可以仔细去看看这道题的题解,博主这一篇博客只打算讲思路,不仔细讲例题。接下来我们看这样一道题:没有上司的舞会

题目描述已经说了,它们的关系像一棵有根树,那我们就在树上DP。这种依赖树形结构的DP我们也把它们划分为一类:

树形DP

这时候可能就有人想问了,既然也是一类DP,它的阶段划分是不是也和区间DP一样,有套路呢?

没错。树形DP依赖树形结构,那么我们很容易想到树的性质,父亲和子节点的关系一一对应,我们可以通过子节点的信息计算父节点的信息。也就是,这类题已经帮我们划分好阶段了,节点从深到浅的顺序就是我们的阶段,我们用一个从上到下的遍历来进行DP,对于每个子节点x先往下递归在它的每个子节点进行DP,再在回溯的时候从子节点向节点x转移状态。这样我们需要做的就是定义状态了。定义状态也很容易,我们一般选择每个节点的编号x作为状态的第一维,再根据不同题目的需求加维进行DP。

回到这道题目上来,我们根据上面的内容,先定义第一维为节点编号,然后我们会发现,一个节点的信息值只与它参不参加舞会有关系,于是我们定义f[x][0/1]为它参加/不参加时的值。

题目也明确说了,如果一个人的直接上司参加了舞会他就不会参加,那我们就可以轻松的得到状态转移方程:
f[x][0]+=max(f[y][0],f[y][1]),f[x][1]+=f[y][0],y∈son(x)

然后递归求解就好了。

写到这里我发现我实在是讲不完所有的DP类型了,于是我们后面会跳过几种DP分为下一篇来谈。

放到下一篇讲的DP(状压DP,计数DP,数位DP,概率与期望DP,所有优化方法)

那么我们就回过来讲DP中一类很特殊的问题:背包问题

什么是背包问题?我们先从基础的0/1背包开始,逐步分析背包问题的模型。

给你n个物品,其中,第i个物品的体积为wi,价值为vi。再给你一个容积为m的背包,现在让你在不超过容积的范围内选出一些物品装入背包让价值尽可能大。

首先我们可能会想到用贪心来解决这道题目,但是贪心很显然是错误的。

我们贪心的策略很显然是每次选择“性价比”最高的物品,也就是wi/vi最大的物品。

但是,对于0/1背包问题,贪心选择之所以不能得到最优解是因为:

它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了

明白这一点后,我们考虑用DP求解。

如何划分阶段呢?很显然,我们依次考虑是否选择每件物品,然后我们还需要知道现在背包用了多少容积。

那我们就设f[i][j]为前i件物品中装了j体积的物品的最大价值。

这里我们用另外一种思路来想转移方程式,我们不分解这个问题,我们直接考虑状态怎么推进。

我们前i件物品中装j体积的最大价值很显然是由前i-1件物品装了某体积的物品,再考虑选不选择当前这件物品转移过来的。

那我们很容易就可以得到如下的转移方程:

f[i][j]=max(

  f[i-1][j],//选这件物品

  if(j>=wi)

    f[i-1][j-wi]+v[i] //不选这件物品

  else

    f[i-1][j] //选不了这件物品

)

阶段:前i件物品(i∈[1,n])

状态:当前的容积(j∈[m,0])

这样我们的空间复杂度是O(n^2)的,考虑优化空间。

我们发现第一位是可以省略的(我们按顺序依次考虑每个物品即可)。

于是...就变成了这样:

for(int i=1;i<=n;i++)

  for(int j=m;j>=w[i];j--)

    f[j]=max(f[j],f[j-w[i]]+v[i]);

 我们每个物品只能放一次,而我们的f[j]要通过f[j-w[i]]计算得到。

如果,我们使用正序循环,从w[i]到m,那么我们可能出现这种情况:

f[j]被f[j-wi]+vi更新过,当我们j增加到j+w[i]时,f[j+w[i]]又有可能被f[j]+vi更新,而同时它们都处于阶段i,也就是说,我们在一个阶段内的两个状态间发生了转移,相当于第i个物品被使用了多次(如果后面又更新了),不符合0/1背包的要求。

所以我们要倒序循环,这样我们的j会一直缩小,不会出现“同阶段间转移”的情况,所以,问题至此得解。

接下来我们要讲完全背包,思考这样一个问题:

给你n种物品,每种物品有无数个,其中,第i种物品的体积为wi,价值为vi。再给你一个容积为m的背包,现在让你在不超过容积的范围内选出一些物品装入背包让价值尽可能大。

注意它和0/1背包问题的区别,0/1背包每种物品只有一个,而完全背包有无数个。

细心的读者可能发现了,我们上面说,当我们正序循环时,相当于一个物品被使用了多次,符合完全背包的要求...那我们只要正序循环,是不是就?

还是太想当然了啊,当然没错。

然后我们讲讲多重背包吧,还是一个类似的问题:

给你n种物品,每种物品有ci个,其中,第i种物品的体积为wi,价值为vi。再给你一个容积为m的背包,现在让你在不超过容积的范围内选出一些物品装入背包让价值尽可能大。

每种物品从无数个变成了ci个,也就是有了限制,怎么做呢?

水题啊!我们把每种物品看成ci个不同的物品不就好了?然后跑一遍0/1背包,问题不久得解了咩?

天真啊,这样的时间复杂度可是 $O(m*\sum\limits_{i=1}^nc_i)$ 的啊(第一次用Latex有点不习惯)

那咋整啊?我们又延伸出了:单调队列优化DP。

DP的种类真是数不胜数...不过优化DP是下一篇的内容,这里不再叙述。

不想用单调队列优化DP来解决多重背包的话,我们可以二进制拆分多重背包。

我们大概是这样一个拆分思路,把每一种物品拆成log个不同物品。

大概是这样拆:

 

int cnt=0;
for(int i=1;i<=n;i++){
    int a,b,c;
    cin>>a>>b>>c;
    for(int j=1;j<=c;j<<=1){
        v[++cnt]=j*a,w[cnt]=j*b;
        c-=j;
    }
    if(c)//剩下拆不掉的部分,直接当新物品
        v[++cnt]=c*a,w[cnt]=c*b;
}

 

思路还是很简单的,但是很巧妙。拆完就是一个0/1背包了,很水。

我佛了...还有个分组背包没讲...这篇博客都写了2天了QuQ,DP真难讲。

限于篇幅和时间,树上的背包问题我留到下一篇的开头...

给出分组背包的模型:

给你n组物品,每组物品有ci个,其中,第i组第j个物品的体积为wi,j,价值为vi,j。再给你一个容积为m的背包,现在让你在不超过容积的范围内每组至多选一个物品装入背包让价值尽可能大。

分组背包是一类树形DP的很重要的组成Part,所以熟练掌握它还是很重要滴。

这个问题我们怎么做呢?

考虑用线性DP解决(...雾),我们要满足“每组至多选择一个物品”的要求,就可以利用“阶段”线性增长的特性,把物品组数作为阶段,只要选了一个第i组的物品,就转移状态到下一个阶段就好了^-^。然后仿照0/1背包的做法,设f[i][j]表示从前i组中选出总体积为j的物品放入了背包,物品的最大价值和。

f[i,j]=max{

  f[i-1,j],//不选第i组的物品喵

  max{f[i-1,j-wik]+vik},(1<=k<=ci)//选第i组的某个物品k喵->是做决策哒

}

上面那东西不是我敲的...但是她不让我删掉

我们还是可以省略掉第一维,别问,问就是这是背包问题。为什么呢?因为我们可以用j的倒序循环来控制阶段i的状态只能由阶段i-1转移得到。

至此问题得解,给出代码:

for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
        for(int k=1;k<=c[i];k++)
            if(j>=w[i][k])
                f[j]=max(f[j],f[j-w[i][k]+v[i][k]);

总结一下,这篇博客我们接触并初步学习了动态规划算法,并对DP的本质有了一定的了解,明白了设计DP算法求解问题的一般思路。没错,设计DP算法,DP算法迷人的地方就在于,对于每道DP题,都需要自己去设计一个合理且高效的DP算法去解决问题,这也是DP难的地方。除此之外,我们还学习了几种常见的DP模型,加深了对“阶段,状态,决策”的理解。DP题要难可以难上天,要简单可以一眼秒,但是本质上看它们都是考察同一个东西:脑子。DP题其实不难个鬼,只要你理解了DP的基本实现方法,稍加思考,把问题转化一下,就很容易想到如何用DP去求解答案。

对于这种依靠思维的题目,其实不用写很多题。虽然刷题是必不可缺的,但是对于写过的每道DP题,都确保自己理解了思路,明白了为什么这样设计DP,就可以总结出一套自己应对DP题的方法和技巧,每个人写DP题的方法都是不尽相同的。希望通过这篇博客,能让你喜欢上动态规划算法。

更深层次难度更高的DP,我会在下一篇博客里讨论。不过就连这篇博客我都写了整整两天,下一篇可能我要写挺久了。除非我够肝

你们的点赞就是我最大的动力(其实我就是想自己整理整理...),感谢你们的支持。

 

 

Guess you like

Origin www.cnblogs.com/light-house/p/11817439.html