動的プログラミングの基本的な考え方
- 動的なプログラミングアルゴリズムは、一般的に最良の特性のいくつかの問題を解決するために使用されます。これらの事項には、多くの実現可能な解決策があるかもしれません。値に各ソリューションの対応は、我々は最適な値を持つ解決策を見つけることを願っています。
- 基本的な考え方は、問題がいくつかのサブの質問、最初のサブ問題解決、元の問題の解決およびこれらのサブ課題から得られた溶液に細分されます解決することです。
- 適した動的なプログラミングの問題を解決するため、問題が頻繁に分解され、サブ互いに独立していません。分割統治法を持つ子どもの数は、この問題を解決する場合は、あまりにも多くの問題は、いくつかのサブ問題の分解が二重にカウント何回もしました。
- 私たちは子供を救うことができる場合は、問題への答えは解決されている、と答えを見つけるために必要として得られているので、あなたは、時間節約、ダブルカウントの多くを回避することができます。我々は解決策を持っているすべてのサブ問題への回答を記録するためのテーブルを使用することができます。それは結果表に記入することが求められる限りとするかどうかは関係なく、サブ問題の将来的に使用されています。
ステップデザイン動的なプログラミング
- プロパティは、最適解を見つけ、前記構造を特徴づけるために、
- 定義された再帰的に最適値(動的プログラミング式が書かれました)。
- ボトムアップ方式で最適な値を計算します。
- 最適値の計算は、最適なソリューションを構築する際に得られる情報です。
PS:ステップ1〜3は、動的プログラミングアルゴリズムの基本的な手順です。のみが必要とする最適値の場合には、ステップ4を省略することができる、問題に対する最適なソリューションを必要とするために、ステップ4が実行されなければなりません。
動的なプログラミングの問題の特徴
:動的なプログラミングアルゴリズムの有効性は、それ自体が持っている問題の二つの重要な性質に依存する
最適なサブ構造:
最適なソリューションは、彼の息子の問題に対する最適解が含まれている場合、問題は構造的な次善を有することを特徴とします。
サブ問題の重複:
トップダウン再帰アルゴリズムソリューションズ、各サブ問題は、常に新たな問題が生じていないときに、いくつかのサブ問題は、反復です。動的なプログラミングアルゴリズムは、各サブ問題のために一度だけこのサブ問題の重なり合う性質上、ソリューションの使用であり、そのソリューションは、可能な限り使用して、これらのサブ問題の解決後、テーブルを保存します。
いくつかの典型的な例
マトリクス乗算問題も
【課題】
与えられたN行列{A1、A2、...、Anは }、 Aiと愛+ 1は乗法であり、iは1,2 = ...、N- 1。計算行列計算を決定する方法であっても必要な最小の数を計算するためにマトリックス乗算の順にしても、製品を作る、製品を注文しますか?
[分析]
最適な順序を計算:計算は[N 1]を調べ;i≤j:行列積もアイアイ+ 1 ... AjがA [J i]のように略記します。
この配列はAkの及びAK + 1行列チェーンが壊れて計算マトリクスとの間に設けられ、1≤k<nは、対応するモードが完全に括弧れる(A1A2 ...アラスカ州)(Akの+ 1Ak + 2 ... AN)。
演算A:とA [K + 1:n]は最高の順である:[1、N]の最適な順序を計算行列A [kは1]ストランド娘に含まれています。
計算:A [1:k]を計算した量プラスA [K + 1:n]の計算、A?[1:k]はプラスとA [K + 1:n]は量を乗じて算出されます
[方法]
研究を設計する[I:J]、1≤i≤j≤n 、 少ない回数によって大部分がM [I、J]は、元の問題mの最適値が必要[1、n]は
iは場合= J、A [I:J] =あい、 したがって、M [I、I] = 0、iが...、1,2 = N
I <J、ときに
再帰的にMを定義することができる[I、J]であります:
[]時間複雑
[コード]
#include<bits/stdc++.h>
using namespace std;
#define NUM 51
int p[NUM],n;
int m[NUM][NUM];
int s[NUM][NUM];
int main()
{
cin>>n;
for(int i=0; i<=n; i++)
cin>>p[i];
for(int r=2; r<=n; r++)
for(int i=1; i<=n-r+1; i++)
{
int j=i+r-1;//计算初值,从i处断开
m[i][j] = m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j] = i;
for(int k=i+1; k<j; k++)
{
int t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if (t < m[i][j])
{
m[i][j] = t;
s[i][j] = k;
}
}
}
cout<<m[1][n]<<endl;
return 0;
}
再帰的なアルゴリズム:
int Recurve(int i, int j)
{
if(i==j)
return 0;
int u=Recurve(i, i)+Recurve(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1; k<j; k++)
{
int t=Recurve(i, k)+Recurve(k+1,j)+p[i-1]*p[k]*p[j];
if (t<u)
{
u=t;
s[i][j]=k;
}
}
m[i][j]=u;
return u;
}
:アルゴリズムの覚書
答え質問が解決しなくても、単純にサブ問題への答えを見つけるために、我々は問題のある子に遭遇したときに、テーブルに解決されている子を保存するための覚書の方法として、動的計画法アルゴリズム。
int LookupChai (int i, int j)
{
if(m[i][j]>0)
return m[i][j];
if(i==j)
return 0;
int u=LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1; k<j; k++)
{
int t=LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
if (t<u)
{
u=t;
s[i][j]=k;
}
}
m[i][j] = u;
return u;
}
第二に、最長共通サブシーケンス
【課題】この
所定のシーケンスX = {X1、X2、...場合 、XM}、 その後、別の配列Zが= {Z1、Z2、... 、ZK}、 Xはサブ存在を指します添字厳密に増加するシーケンス{I1、I2、...、IK } ように全てのj = 1,2ため、...、K有する:ZJ = xijを。
二つの配列XとYを考えると、サブシーケンスは、XとYの両方の別のシーケンスは、時間のサブシーケンスであるZ、彼はZは、XとYの順であることを特徴とする一般的なサブシーケンス。
与えられた二つの配列X = {X1、X2、... 、XM} とY = {Y1、Y2、... 、Ynが}、 X及びYであるの最長共通部分列を検索します。そして、このシーケンスの出力。
[分析]
リセットシーケンスX = {X1、X2、... 、XM} とY = {Y1、Y2、... 、YNは} である最長共通部分列Z = {Z1、Z2、... 、ZK}、 その後、
1) XM = YN場合、ZK = XM = YN、およびZK-1 XM-1は、最長共通部分列和YN-1です。
2)もしXM≠YN及びZK≠XM、次にZは最長共通サブXM-1およびY.
XM≠YN及びZK≠YN場合3)、その後、Z及びX YN-1は、最長共通サブシーケンスです。
すなわち:
[コード]
#include<bits/stdc++.h>
using namespace std;
int n,m;
char a[10010],b[10010];
int dp[10010][10010],x[10010][10010];
vector<char>v;
void LCS(int i,int j)
{
if(i==0||j==0)
return;
if(x[i][j]==1)
{
LCS(i-1,j-1);
v.push_back(a[i]);
}
else if (x[i][j]==2)
LCS(i-1,j);
else
LCS(i,j-1);
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=m; i++)
cin>>b[i];
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(a[i]==b[j])
{
dp[i][j]=dp[i-1][j-1]+1;
x[i][j]=1;
}
else if (dp[i-1][j]>=dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
x[i][j]=2;
}
else
{
dp[i][j]=dp[i][j-1];
x[i][j]=3;
}
}
}
cout<<dp[n][m]<<endl;
LCS(n,m);
for(int i=v.size()-1;i>=0;i--)
cout<<v[i]<<" ";
cout<<endl;
return 0;
}
第三に、サブセグメントと最大
【課題】
配列には、最大値とサブセグメントのシーケンスを選択、...、A2、からなる(負の整数を含む)n個の整数a1で決まります。
すべてが負の整数およびゼロのときに最大のサブセグメントを定義します。
最適値が必要:
[分析]
BJ 1に最大サブセグメントの位置とjである。
そうでなければBJ = AJ BJ-1> 0 BJ = BJ-1 + AJ、あれば容易に、BJによって定義された知っています。
BJは、動的プログラミング再帰計算される:BJ =最大{BJ-1 + AJ、AJ}、1≤j≤n。
[コード]
#include<bits/stdc++.h>
using namespace std;
int n,a[100010];
int b,sum,l,r,s;
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++)
{
if(b>0)
b+=a[i];
else{
b=a[i];
s=i;
}
if(b>sum)
{
sum=b;
r=i;
l=s;
}
}
cout<<l<<" "<<r<<" "<<sum<<endl;
return 0;
}
第四に、0-1ナップザック問題
【課題】この
セットアイテムS = {1,2,3、...、Nが与えられる }、 iはウィスコンシンアイテムの重量値はVIである、Wのナップザックの容量、すなわち、最大負荷W.以下で計量しません 限られた総重量Wの中で、我々は、アイテムの最大合計値を作るために、項目を選択する方法。
アイテムは分割できない場合、すなわち、いずれかの私が選択または選択しないように物品全体、袋に私の記事を繰り返しことができない、物品の一部のみがIで充電することができない、問題は0-1ナップザック問題と呼ばれます。
アイテムを分割することができます場合、問題はナップザック問題、使用するのに適して貪欲アルゴリズムと呼ばれています。
[分析]
Pの最適値(i、j)はi≤k≤n。
バックパックの容量jは、I、I + 1、...、任意項目である N とき0-1ナップザック問題の最適値。
[コード]
#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[100010],w[100010],v[100010];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i]>>v[i];
memset(f,0,sizeof(f));
f[0]=0;
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]);
int ans=0;
for(int j=0; j<=m; j++)
ans=max(ans,f[j]);
cout<<ans<<endl;
return 0;
}