DPとメモリ・サーチ・配布資料リニア非常に完全

基本的な考え方

私たちは、動的計画法の最も基本的な間、前のレッスンに連絡しました。
動的計画最も重要なことは、状態と状態遷移方程式を見つけることです。
重複していない後遺症のサブ問題、最適なサブ構造など:また、動的なプログラミングの問題の分析は、次のようないくつかの重要な特性は、あります。

最適なサブ構造の概念:
1)問題の最適解は、問題のサブ最適なソリューション、最適なサブ構造に問題が含まれている場合。最適な下部構造に問題が、我々は、動的プログラミングを使用する場合があります場合は(貪欲な戦略でもある可能性が適用します)。

2)最適な下部構造の検索は、一般的なパターンをたどることができます。

  • 問題を解決するには、オプションかもしれません。例えば、組立ステーション選択。
  • 与えられた問題についての仮定は、最適なソリューションの選択をもたらすことが知られています。あなたは彼が知られていると仮定すると、この選択を決定する方法を気にしないでください。
  • この選択の後、これらのサブ問題の結果として起きるを識別することが知られており、どのように最高の得られた子の問題空間を記述することです。
  • 「カット・アンド・ペースト」問題に対する最適解を実証するための手法を使って、子供自身の使用の問題の解決策が最適でなければなりません。

3)2つの方法で最適なサブ構造は、問題のドメインに変更します。

  • どのように多くのサブ問題は、元の問題に最適なソリューションで使用される、とします
  • どのように多くで最適解を決定する際にそれらのサブ問題を使用する際に選択します

組立ラインのスケジューリング問題では、最適なソリューションは、我々は二つの選択肢を考慮する必要があり、最適なソリューションを決定するために、しかし、一つだけのサブ問題を使用します。

4)動的プログラミングと貪欲法の違いを

  • 最適なサブ構造を用いたボトムアップ方式での動的プログラミング。すなわち、第一の問題に対する最適解を見つけ、その後、子供が問題を解決し、問題のある子の最適解を見つけること、です。、サブ問題の選択を行い、問題を解決するために使用するかを選択するための最適なソリューションの必要性を見つける問題。コストのためのソリューションは、通常のサブ問題のコストに加え、自分の選択を持参するコストです。
  • 貪欲アルゴリズムで最適な下部構造を用いたトップダウン方式に基づいています。欲張りアルゴリズムは初めてで、今の問題を探している最良の選択ではなく、サブ最適解よりも、問題の解決に、その後の果実のように見えるか、選択してから、選択を行うだろう。

サブ問題の重なり合う概念を:
ほとんどの動的最適化問題解決のためには2番目の要素は「非常に小さい」と宇宙サブ問題である持っている必要があり、元の問題を解決するための再帰的なアルゴリズムは、同じ子のために使用され、繰り返し解決策になることができます問題は、常にではないが、新たなサブ問題インチ
例:ステータス\(I \)解くとステータス(I-1 \)\の状態に関連した(I-1 \)\解くとステータス\(I-2 \) その後、我々は状態を、関連する計算するとき\( I \) 我々が使用することができます(F [i]が\)\状態を表すために、私は\(\) そして次回は、私は状態を使用する必要がある場合、\(私は\)私が直接返されたときに\(F [I] \)することができます。

いいえ後の効果コンセプト:
決定したら、ステージの状況、発生がもはや簡単に言えば、様々な状態や、以前の意思決定に影響された後は、現在の状態が以前の歴史で、「未来は過去とは何の関係もありません」ではありません唯一の現在の状態によって、プロセスの将来の発展に影響を与える前の歴史の完全な要約。問題は、位相状態がIのみ状態における位相I-1から転送された状態方程式を通ってくる、各段階の後に分割されている場合、具体的に、他の状態とは関係がない、特定の状態とは関係は発生しません。転送状態定義及び図面は、この問題の頂点との間に画定されている場合、エッジなどの2つの状態を考慮するグラフ理論の観点から、重量増加は、エッジ重みの転送中に定義され、構成有向非巡回重み付けグラフは、したがって、マップはそのトポロジカルソートの順に少なくとも相を分割し、「トポロジー的ソート」であってもよいです。

私たちは主にDP法リニアメモリメモリの解決では、この講演の中で最も基本的なの。
実際には、我々はそれは、動的プログラミングの再帰バージョンになり、解決策は、メモリの検索に追加された場合、その後、彼は、「最適なサブ構造」と「重複したサブ問題」を解決することがわかります。

説明:
私たちは循環とメモリのためのソリューションを説明します次の例では、これらの質問の文言を検索します。
私たちのために、ループ内の文言が、このレッスンは、より便利に、よりよく理解され、それらを書きますが、必ず学生が理解し、習得することにしたいメモリ検索私たちの次のコースのセクションでは、メモリは非常に重要な関係を持って検索しますので、文言を。

例1は、最も長いシーケンスを上昇します

トピック効果:
あなたの長さを与えるためには、\(N \)列の数です\(A_1、A_2、\ cdots、A_N \)には、その最長の上昇、配列の長さを見つけます。
交換順序なしの配列:配列上昇\(A \)ので、後者の要素よりも前部要素は、必ずしも小さいこと(サブシーケンスが連続的である必要はない)要素の数を選択します。最も長いシーケンスの増加に対応することは最も長いシーケンス上昇です。
私たちは、一般的には「最長の上昇シーケンス」と呼ばLIS(最長増加部分列)。

問題解決のアイデア:
リセット状態\(F [I] \)は、で表される(a_iを\)\端(および含む\(a_iを\) )、その後、最も長い系列の長さを増加させました。

  • \(F [i]が\)である少なくとも\(1 \)
  • \(F [I] = \ MAX(F [J])\) +。1、\(J \)を満足\(0 \ルJ \ LT I \) と\([J] \ LT A [I ] \) 。

コードは示してい

まず、アレイと、必要な変数を定義します。

int n, a[1010], f[1010], ans;

どこで:

  • \(N- \)配列要素の数を表します。
  • \(A \)の値を格納するためのアレイは、\(A [I] \)は、配列を表し\(I \)要素の値を、
  • \(F \)状態を記憶するためのアレイ、\(F [I] \)で表される\([I] \) LIS長の終了時に、
  • \(ANS \)は、私たちの最終的な答えを格納するために使用されます。

私たちは、その後、入力を処理します:

cin >> n;
for (int i = 1; i <= n; i ++)
    cin >> a[i];

その後、我々はあなたがforループで解決実現を示し(F [1] \)\\(F [N-] \)

for (int i = 1; i <= n; i ++) {
    f[i] = 1;
    for (int j = 1; j < i; j ++) {
        if (a[j] < a[i]) {
            f[i] = max(f[i], f[j]+1);
        }
    }
}

そして、私たちの答えは\(F [i]が\)最大:

for (int i = 1; i <= n; i ++)
    ans = max(ans, f[i]);
cout << ans << endl;

次に、どのようにそれを達成するための方法の検索+メモリを使うのか?次のとおりです。

int dfs(int i) {
    if (f[i]) return f[i];
    f[i] = 1;
    for (int j = 1; j < i; j ++)
        if (a[j] < a[i])
            f[i] = max(f[i], dfs(j)+1);
    return f[i];
}

メモリの検索は、としても知られている覚書私たちがここに持っている覚書、私たちです\(F [i]が\)

  • 場合dfs(i)それは、初めて呼び出されたとき(F [I] = 0 \)\、一連の計算を行います。
  • しかし、もしdfs(i)それが呼び出された最初の時間は、バインドされていない([I] \ GT 0 \ F)\、それはdfs(i)直接返す\(F [i]が\)このように計算サブ再読み取りの問題を回避し、値。

関数の先頭で裁判官だった私は:
もし\(F [i]が\)は、その後、直接返され、ゼロではない(F [i]が\)\
そうでなければ計算。

その後、我々は次のように答えを計算することができます。

for (int i = 1; i <= n; i ++)
    ans = max(ans, dfs(i));
cout << ans << endl;

完全なコードの一般的な形式:

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++) {
        f[i] = 1;
        for (int j = 1; j < i; j ++) {
            if (a[j] < a[i])
                f[i] = max(f[i], f[j]+1);
        }
    }
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[i]);
    cout << ans << endl;
    return 0;
}

完全なコードフォームのメモリ検索:

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
int dfs(int i) {
    if (f[i]) return f[i];
    f[i] = 1;
    for (int j = 1; j < i; j ++)
        if (a[j] < a[i])
            f[i] = max(f[i], dfs(j)+1);
    return f[i];
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++)
        ans = max(ans, dfs(i));
    cout << ans << endl;
    return 0;
}

実施例2および最大フィールド

効果件名:
として私たちができる「場」「連続シーケンス。」
最大フィールドと問題が解決され、最大の間のすべての連続したサブシーケンスと。

ソリューションの概要:
最初に、我々は、状態定義\(F [i]が\)で表される([I] \)\端(および含む\(A [I] \) と最大フィールド。
その後、我々は、状態遷移方程式を得ることができます
\([1-I] Fを[I] = \ MAX(F、0)+ A [I] \)

(座標からまず、入力部を初期化すると、以下の\(1 \)\(N- \) ):

int n, a[1010], f[1010], ans;
cin >> n;
for (int i = 1; i <= n; i ++) 
    cin >> a[i];

そして、次の一般的な方法を解決する方法で:

for (int i = 1; i <= n; i ++)
    f[i] = max(f[i-1], 0) + a[i];

そして、私たちの答えは、すべてのです\(F [i]が\)最大:

for (int i = 1; i <= n; i ++)
    ans = max(ans, f[i]);
cout << ans << endl;

再帰的なフォームは、我々はまた、機能を開いdfs(i)返すための\(F [i]が\)の値を。
しかし、ここで我々が通過することはできません(\ [I] F)を\値が決定\(F [i]のを\)私はbool型を開きますので、すでにアウト求めている\(VIS \)で配列(VIS [I \ ] \)が決定される(F [i]が\)\オーバー要求するかどうか。

bool vis[1010];

以下を達成するためのメモリ検索:

int dfs(int i) {
    if (i == 0) return 0;   // 边界条件
    if (vis[i]) return f[i];
    vis[i] = true;
    return f[i] = max(dfs(i-1), 0) + a[i];
}

注:境界条件に検索/再帰必見の注意を払います。

その後、次のようにモード1を解決するための答えは次のとおりです。

ans = dfs(1);
for (int i = 2; i <= n; i ++)
    ans = max(ans, dfs(i));
cout << ans << endl;

次のように答えを解決するための別の方法は次のとおりです。

dfs(n);
ans = f[1];
for (int i = 2; i <= n; i ++)
    ans = max(ans, f[i]);
cout << ans << endl;

ここで私は呼ぶだろう、そこに見つからない\(\)(N- DFS) のすべて([I](1 fは\ \ルI \ルN)\) すべてはまだ模索に値が。
そのため、私の最初のシーク\(DFS(N)\)の呼び出し\(DFS(N-1) \) 、そして初めて\(DFS(n-1)の \) と呼ばれます\(DFS (N - 2)\)、......、初めて\(DFS(2)\)と呼ぶことにする)\(DFS(1)\

だから、呼び出し\(\)(N- DFS)は、私はすべての入れ(F [i]が\)\うち求めています。

次のように完全なコードの一般的な形式は以下のとおりです。

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++)
        f[i] = max(f[i-1], 0) + a[i];
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[i]);
    cout << ans << endl;
    return 0;
}

次のようにコードメモリ検索の完全な実装は、以下のとおりです。

#include <bits/stdc++.h>
using namespace std;
int n, a[1010], f[1010], ans;
bool vis[1010];
int dfs(int i) {
    if (i == 0) return 0;   // 边界条件
    if (vis[i]) return f[i];
    vis[i] = true;
    return f[i] = max(dfs(i-1), 0) + a[i];
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    // ans = dfs(1);
    // for (int i = 2; i <= n; i ++)
    //     ans = max(ans, dfs(i));
    dfs(n);
    ans = f[1];
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[i]);
    cout << ans << endl;
    return 0;
}

タワーについての例の3ナンバー

影響を受ける:
以下に示す、基礎となる要件は、上から来るように塔の数があり、各ステップは、図面を通じて隣接ノード、ノード場合に来て、最大数であることができますか?

問題解決のアイデア:

まず、我々はいくつかの仮定します:の合計\(N \)行、行の先頭が最初です\(1 \)のラインを、一番下の行が最初ということです\(N \)ライン、我々が使用\((I、 J)\)部分を示すために\(I \)\(J \)で、格子を[I] [J(\ )\] を表し((i、j)を\ \ ) 格子に格納された値。

私たちは、それから見ることができます(\(i、j)は\ ) にボトムアップと散歩から降りて徒歩の底部とグリッドの最大数(\(i、j)は\ ) の最大数をしています等しいです。
グリッドからほとんどすべての要求の下で(1,1)まで歩いて、最大数:だから我々はなり質問を置くことができます。
思考の転換を通じて見て、私たちは「ボトムアップ」の問題に変換し、「トップアップ」の問題を入れて、見つけることができます。
(良いの経験持っていてください「トップダウン」「ボトムアップ」我々は別のシナリオでは二つの概念の横にこの質問を議論するように、これら二つの概念を)

我々は最低レベル(最初に加えて、見つけることができます\(I \)層)を直接、すべてのアッパー、事故に行ってきましたさ(\(i、j)は\ ) からよりも\((I + 1、J )\) 思い付い、それはから、です\((I + 1、J + 1)\) 過去に。

したがって、我々は仮定してもよい(F [I] [jが\ ] \) 歩く地面から任意の位置を表す(\(i、j)を\ ) の最大数と位置を。

これは、推測することができます。

  • \(i=n\) 时,\(f[i][j] = a[i][j]\)
  • \(I \ LT N \)时、\(F [I] [J] = \ MAX(F [I + 1] [J]、F [I + 1] [J + 1])+ [I ] [J] \)

リコールから推定する過程で\(\ N-)\(1 \)トラバース\(I \)のハイレベルの状態は、第1の低レベル状態によって誘導されるからです。

メインコードセグメント実装以下の一般的な形成します。

for (int i = n; i >= 1; i --) { // 自底向上
    for (int j = 1; j <= n; j ++) {
        if (i == n)
            f[i][j] = a[i][j];
        else
            f[i][j] = max(f[i+1][j], f[i+1][j+1]) + a[i][j];
    }
}

見つけることができる、我々は書かれたの一般的な形式を使用し、下層を解決するために移行され、その後、私たちはこのアイデアが達成されると言うことができるように、過渡のハイレベルによって低レベル状態を導き出す下から上へ。

実装の一般的な形式を終え、のの使用説明させ、検索のメモリ実装の形で解決されたが。

我々はまた、静止状態を定義する必要があり(F [I] [jが\ ] \) の位置を表す底のいずれかから来る((i、j)を\ \ ) 数および最大(及び上述したのと同じ)。

しかし、我々は解決するために、上記の一般的な形ではありません(F [I] [J] \)\が、関数を開き、dfs(int i, int j)解決するために(F [I] [J] \)\

だから、どのように我々はにやるのメモリ、すなわち:現在決定するために\(F [i]の[jは ] \)はすでにそれを訪問しているの?

開始のために、\(F [I] [J ] \) である\(0 \)列のすべての要素の数と、\([I] [jが 】\) であった(\ GT 0 \)\、次に\(F [I] [J ] \) を介して、要求に応じて\(F [I] [J ] \) も必要\(\ GT 0 \)

しかし、\(A [I]は[J] \ GE 0 \) すなわち\([I] [J ] \) であってもよい)\ 0(\)または\([I] [J ] \) CAN負の数の場合には、我々はに頼ることはできません(F [i]が[J \ ] \) かどうか(0 \)\を決定するために\((i、j)は\ ) このグリッドは、(訪問していない、慎重に考えをなぜ)。

最も信頼性の高いので、最も困難な方法は、2次元開く、実施例2と同じ方法を使用して間違っている\(VIS \)で、配列を(VIS [I] [J \ \]) を識別するために((\ I 、J)\)あなたが訪問しました。

フラグメントメインメモリ検索フォームを次のように

int dfs(int i, int j) { // dfs(i,j)用于计算并返回f[i][j]的值
    if (vis[i][j]) return f[i][j];
    vis[i][j] = true;
    if (i == n) // 边界条件——最底层
        return f[i][j] = a[i][j];
    return f[i][j] = max(dfs(i+1, j), dfs(i+1, j+1)) + a[i][j];
}

完全なコードの一般的な形式は次のとおりです。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
int n, a[maxn][maxn], f[maxn][maxn];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            cin >> a[i][j];
    for (int i = n; i >= 1; i --) { // 自底向上
        for (int j = 1; j <= n; j ++) {
            if (i == n) f[i][j] = a[i][j];
            else f[i][j] = max(f[i+1][j], f[i+1][j+1]) + a[i][j];
        }
    }
    cout << f[1][1] << endl;
    return 0;
}

メモリは、完全なコードを検索し、次のとおりです。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
int n, a[maxn][maxn], f[maxn][maxn];
bool vis[maxn][maxn];
int dfs(int i, int j) { // dfs(i,j)用于计算并返回f[i][j]的值
    if (vis[i][j]) return f[i][j];
    vis[i][j] = true;
    if (i == n) // 边界条件——最底层
        return f[i][j] = a[i][j];
    return f[i][j] = max(dfs(i+1, j), dfs(i+1, j+1)) + a[i][j];
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            cin >> a[i][j];
    cout << dfs(1, 1) << endl;
    return 0;
}

おすすめ

転載: www.cnblogs.com/quanjun/p/12157637.html