DP動的研究プログラミング上の注意

最も広範な研究はアルゴリズムの最大数を調査すると、当然のことながら、それを確認するためにブログを開きます。

孔子は言った:古の確認、教師としての役割を果たすことができます

DP私はDPの自分の理解のいくつかを検討する際、また、それを共有します。

- 正スタート -

動的プログラミングアルゴリズム、すなわち、動的プログラミング(以下、DPと呼ぶ)は、多段階の意思決定プロセスの最適化問題を解決するための非常に効率的な数学的方法です。1999年のタイトルIOIは広くOI競争の中で循環上のDPタイトルの「デジタル・トライアングル」と呼ばれているので。上記「デジタル三角形」、今エントリ-DPのタイトルです。

再発との関係のDP:

多くの人々は、再帰とDP、DPのちょうど再帰的な実装を混乱させる。私たちが言及したDPは、再帰検索し、メモリを介し主に達成するための効率的なアルゴリズムですが、DPのような多くの再帰的な点は、彼らはサブの問題を使用して元の問題を取得するためにある、あります。

例を挙げましょう、フィボナッチ数、私は誰もが精通していると信じています。

第一項と第二X 1 X 2アイテム発売アイテムXによって、F(X)= F(X-1)+ F(X-2):我々は、のFIB漸化式を知っています。

DPのFibの我々斜視た場合、我々は、我々は、サブ問題などの項目の第一項のx-1とx 2の要件を見つけ、アイテムxとして、元の問題を見つけることができ、我々は、アイテムxを見つける」置きます第X-1項目を求めて「と」第X-2二つのサブ問題」のを求めて「元の問題は、に分割されている」、その後解決するために分割し、副問題に進み、最終的に元の問題の全てのサブ問題のソリューションを活用します。

多くのDPのブログエントリは慎重自然、問題解決のステップとDP DPの使用の三つの基本的な機能を説明します。

これらを理解していない、私は、このセクションでお見せするmengmengdastyleのブログ@からピックアップ:

(2)動的プログラミングは、三つの重要な概念を含む
最適下部-
-バウンダリ
-状態遷移方程式
(3)一般的な手順を解決される:
1。構造的特徴および最適を特徴付ける最適解性を、検索を前記下部構造;
2.再帰的に定義された最適値、溶液及び下位問題の元の問題の解決策との関係を示す図である。
3.ボトムアップファッション計算各サブ問題、元の問題の最適値、及び回避副問題に二重カウント;
4.最適値算出情報を取得し、最適なソリューションを構築します。
動的プログラミング特性を使用して(4)
1シーク最適ソリューション
2.大きな問題は、下位問題に分解することができ、ならびにより小さな部分問題の重複部分問題
の最適なソリューションは、全体的な下位問題に依存3最適解(状態遷移方程式)
問題を解決するためのボトムアップからの問題ダウンの分析から4、
ディスカッション境界の根底にある問題を

上記。

アイデアや問題解決能力、多くの繰り返しのための基本的な知識をこの記事の焦点。

のは、質問を分析し、あなたがやってに使用されているDPでこの質問を理解してみましょう。

あなたのx(1 <= X <= 2×10 ^ 5)階段の高さを与え、各レベルは1またはレベル2に上がることができ、あなたはトップに行って、どのように多くの移動の合計を求めました。

サンプル入力:

10
サンプル出力:

89

X = 1場合のは、x = 2の場合、それは1に等しく、あなたは簡単に取得することができます答え、問題を分析してみましょうか?答えは2であり、取得するのは非常に簡単。

私たちのxは素晴らしいですが、我々は結果が各xの手のために脇に置くされている計算することができません。

私たちは、分解の問題を考えるので、元の問題は、各サブ問題の分解ダウンのために、いくつかのサブ問題に分解されます。

「の矮小化するもの。」

次に、どのように我々はこの問題を打破するのですか?私たちは移動の数を取る場合は、まずすべての参照の私たちは一緒にプログラムレベル3の数をこすりために家に行くために、レベル1とレベル2のプログラム番号で行くことができれば、3のレベルが必要でした。したがって、我々は確かに、見つけます。私たちは、レベル3が移動+徒歩数2を歩くクラスの1つのレベルの数を取ることですいくつかの動きを歩きました。

この問題に対する解決策も:数離れステージが移動するX-2レベルに移動するの数ダウン数= X-1 +ステージ移動をダウンxは。

この質問は、フィボナッチ数を求めている、我々は答えを得る再帰的な使用します。

現在では、より困難な問題を紹介し、私はDP自身について話をするつもりです。

競争の中でOI、我々は答えを計算するためにコンピュータを使用しています。しかし、我々は唯一の状態のいくつかを記録するためにコンピュータを使用することができ、我々は国家として問題を定義するために試してみたいので、状態のレコードを解決するために使用する必要があります。状態の必要性「再帰的」の定義を言ってDPの時の話を多くの人々、。ナンセンス:この引数には、我々は一般的に記述するための2つの単語を使用します。私たちは実際にそれをどのように定義するかを考えることで、我々は最初にすべての行う必要があり、状態を定義する必要がありますが、すべての問題は、状態として定義することができ、一般的には、変数を考慮したもの、変数は、我々は保つかについて考えることです、どのようにこれらの変数が来ますソリューション。

私たちは、一般的に各ステージが異なるステータスを持って、異なる段階に問題を分割します。しかし、我々はすべての国が生き残る計算する必要はありません、私たちはライン上で最良の答えを維持するために必要なすべてのステップは、これが最適解を求めるためのDPの理由です。問題は、ステージや状態に分けることができますので、その後、最適解特定のステージは、最適なソリューション前の段階を介して取得することができるようになります。

しかし、我々は唯一の答えの現在のフェーズの前段階を経て答えるならば、それを数えることができませんでしたか?我々はそれのすべての段階の前に答えるために必要がある場合は?

問題の各段階で、状態は複数の状態の次のステージに転送することができるならば、我々はこの問題を解決するためにDPを使用しないことを、解決策は複雑のインデックスレベルであることを時間を計算します。これは、意思決定の前に、それが呼ばれ、後者の場合に影響を与えます後に効果があります。

DPは、それの3つの要素の一つを覚えていますか?後のない効果なし。

それのエントリのタイトル検索を覚えていますか?01迷路。

我々は最初から最後までの最短経路を検索したい場合は、我々はそれだけの現在のフェーズの状態を保存することができますか?明らかではありません。我々は時間の状態を保存しない時間を考えますか?{X、Y、ステップ}、およびVISアレイ。旅の前に、我々は最短経路を求めることを要求の対象であるので、我々はすべての場所を知っている必要があります。

我々は別のルートを取るが、また、私たちは後ろに行くことを選択したパスに影響を与える前に、私たちは、同じ位置に現在ある場合でも、我々はグリッド気力マークの上に行く必要はありませんので

私たちは、各ステージ経験したソリューションのすべての状態を保存する必要がありますので、次の段階を取得します。

これは、残効は、問題の例です。

如果我们需要记录之前所有的状态,我们的复杂度就是指数级的,但是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,我会在下一篇博客里讨论。不过就连这篇博客我都写了整整两天,下一篇可能我要写挺久了。除非我够肝

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

 

 

おすすめ

転載: www.cnblogs.com/light-house/p/11817439.html