トピックに直接移動します
ハンディキャップのため、投稿を何度も削除するので、それらの鐘や笛を鳴らしたくありません
前書き:
今週、動的計画について学びました。食べ物が多すぎて強化できないので、投稿したいと思います。
あまりにも料理
コンセプトの最初の部分:
主に分割:
- 状態および状態変数
- ステージとステージ変数
- 戦略と最適戦略
- 状態遷移方程式
私をつついて
それでもわからない場合は、情報を参照してください
実際、DPは再帰に似たアルゴリズムです。
概念を読んだ後、コーディングを開始します。
質問1:最も長い昇順のサブシーケンス(時間の複雑さO)
問題の
説明:
シーケンスが与えられた場合、この番号のグループで構成されるシーケンスaがi <jおよびa [i] <a [j]を満たすように、シーケンスからいくつかの番号を選択し、このシーケンスの最長の長さを見つけます。
#include <cstdio>
const int MAXN = 5005;
int a[MAXN], dp[MAXN];
int main() {
int n;
scanf ("%d", &n);
int ans = 0, s = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
dp[i] = 1; //初始化
for(int j = 1; j < i; j++) {
if(a[i] > a[j] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
}
}
if(dp[i] > ans) {
ans = dp[i];
s = i;//转移答案
}
}
printf("%d\n", ans);
return 0;
}
それでは説明しましょう。まず、DP配列を定義します。dp[i]は、iで終わる最長の昇順サブシーケンスの長さを表します。これはいわゆる状態です。その後、ダブルループを使用し、ループスキャンの後で、iで終わる現在の最長の昇順サブシーケンスを見つけて判断し、見つかった最長の昇順サブシーケンスの長さを割り当てます。ループして最適なソリューションを順番に取得します。ループは終了します-この質問は終了します。
次に、(n * logn)の解を知りたい場合。
自分で考えてみてください。うまくいかなければ仕方がありません。要するに、次回はステージに上がります。
したがって、問題は、動的プログラミングがいつ使用されるのかということです。
GMの信頼できる説明:問題の状態遷移方程式に最適な下部構造と重複するサブ問題がある場合、動的プログラミングによって解決できることがわかっています。
これは貪欲とは対照的です!!
次に、次の質問を開始します。
最長の昇順サブシーケンスの出力シーケンス
トピックゲート
タイトルの説明:
整数シーケンスA1A2A3 ... Anが与えられます。サブシーケンス内の要素の数ができるだけ多くなるように、そのサブシーケンスの増加を見つけます。要素は必ずしも連続している必要はありません。
(ここで特に注意してください:
出力形式
。1行目:1つの整数k、最長の昇順サブシーケンスの長さを示します
。2行目:k個の整数は、単一のスペースで区切られ、最長の昇順サブシーケンスが見つかったことを示します。長さがkに等しいサブシーケンスの場合、最初のサブシーケンスが出力されます。
)
次にコードします!(WAコード)
#include <cstdio>
int prev[1005];
const int MAXN = 100005;
int a[MAXN], dp[MAXN];
using namespace std;
void print(int i){
if(prev[i]==i){
printf("%d",a[i]);
return;
}
print(prev[i]);
printf(" %d",a[i]);
}
int main() {
int n;
scanf ("%d", &n);
int ans = 0, s = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
dp[i] = 1;
for(int j = 0; j < i; j++) {
if(a[i] > a[j] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] +1;
prev[i]=j;
}
}
if(dp[i] > ans) {
ans = dp[i];
s = i;
}
}
printf("%d\n", ans);
print(s);
return 0;
}
なぜここが間違っているのか、どこが間違っているのですか?コンパイルに行くことができます。なぜ間違っているのかをお話ししましょう。ここでは、前任者を意味するprve prev [i]という関数を紹介します。彼の利点は無数にあります、ここであなたはチェックに行きます。しかし、なぜそれが間違っているのですか。ループ内でprev [i]は毎回= 0でなければなりませんが、その時点では追加していません。prev[i] == iが初期化であると考えて、私はまだだったようです。初期化の意味がわからなかったので、0に戻らなければならないので、すべてのテストポイントに0を追加します。!!!私は本物の料理のようです
嘲笑のラウンドに勝った後、私のACコードは次のとおりです。
#include <cstdio>
int prev[1005];
const int MAXN = 100005;
int a[MAXN], dp[MAXN];
using namespace std;
void print(int i){
if(prev[i]==i){
printf("%d",a[i]);
return;
}
print(prev[i]);
printf(" %d",a[i]);
}
int main() {
int n;
scanf ("%d", &n);
int ans = 0, s = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
dp[i] = 1;
prev[i]=i;
for(int j = 0; j < i; j++) {
if(a[i] > a[j] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] +1;
prev[i]=j;
}
}
if(dp[i] > ans) {
ans = dp[i];
s = i;
}
}
printf("%d\n", ans);
print(s);
return 0;
}
それでは、非常に古典的な例について話しましょう
質問3:デジタルトライアングル
したがって、この質問にはいくつかの異なるアプローチがあります。
- 検索する
- メモリ検索
- DP(フォロー)
- DP(リバースプッシュ)
トピック
時間の関係で、主に4番目のソリューションについて説明します。
見てみましょう。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[105][105] = {
0};
int dp[105][105] = {
0};
int main(){
int n;
cin>>n;
for( int i = 1; i <= n; i++ )
for( int j = 1; j <= i; j++ )
cin>>a[i][j];
for( int i = 1; i <= n; i++ )
for( int j = 1; j <= i; j++ )
dp[i][j] = max(dp[i-1][j-1],dp[i-1][j]) + a[i][j];
int ans = 0;
for( int j = 1; j <= n; j++ )
ans = max(ans,dp[n][j]);
cout<<ans<<endl;
return 0;
}
それでは、それを分析しましょう。欲が使えない理由は、彼には近視眼的で欲張りができるという欠点があるからですが、それは正しくありません。欲は13–11–12–14–13であり、合計は63であることがわかりますが、彼にはそのような彼と86をロードします。これが貪欲である必要がない理由です。次に、DPで解決する方法を見てみましょう。
1つ目は、ステージを分割することです。三角形の行に従ってステージを分割します。n行ある場合、n-1ステージがあります。
1. 从根结点13出发,选取他的两个方向中的一条支路,当到了倒数第二层时,每个节点其后继仅有的两个节点,可以直接比较,选择最大值为前进方向,从而求得从根节点开始到底端的最大路径。
2. 自底向上:(给出递推式和终止条件)
-从底层开始,本身即为最大数
-从倒数第二层开始取决于底层数据
······
由此推下去
最も重要なことは、状態と状態遷移の方程式です。このスキームはあまり反映されていませんが、それについて話す必要があります。
ステータス:Ans = max {F [N] [1]、F [N] [2]、····、F [N] [N]}
状態遷移方程式:F [y] [y] = max {F [x-1] [y-1]、F [x-1] [y]} + A [x、y]
境界条件:F [1] [1] = A [1] [1]
状態遷移方程式の「-1」があいまいな場合もありますが、より透明に感じられます。
それでは、デジタルトライアングルの他の方法について引き続きお話ししましょう
次に、2番目の解決策を開始します-検索は単に検索します
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=1005;
int a[MAXN][MAXN],f[MAXN][MAXN],n,ans;
void dfs(int x,int y,int Curr){
if(x==n){
if(Curr>ans)ans=Curr;
return;
}
dfs(x+1,y,Curr+a[x+1][y+1]);
dfs(x+1,y+1,Curr+a[x+1][y+1]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&a[i][j]);
ans=0;
dfs(1,1,a[1][1]);
printf("%d",ans);
return 0;
}
この質問にこの方法を使用すると、制限時間を超えて30ポイントを超える可能性があります。
このコードがタイムアウトすることを知るためにコンパイルする必要はありませんが、なぜタイムアウトするのですか?彼は、各パスがされているので、すべてのアレイを歩いたので、からなるN-1 、各ステップが有するの二つの選択肢左と右の合計* 2であるので、時間計算量はOであるので、N-1 * (2 n-1)、間違いなくタイムアウトしました。
次に、3番目のソリューションは検索を記憶しました
2番目の方法がタイムアウトする理由は、すべての条件が検索されるため、記憶された検索が使用されるためです。
注意!今日はGMに聞いてみましたが、実はダイナミックプログラミングは暗記検索と理解できるのでわかりやすくなります。
さて、コードで:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=505;
int a[MAXN][MAXN],f[MAXN][MAXN],n;
int dfs(int x,int y){
if (f[x][y]==-1){
if(x==n)f[x][y]=a[x][y];
else f[x][y]=a[x][y]+max(dfs(x+1,y),dfs(x+1,y+1));
}
return f[x][y];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++){
scanf("%d",a[i][j]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
f[i][j]=-1;
dfs(1,1);
printf("%d",f[1][1]);
return 0;
}