2019-07-30
Educational DP Contest A
こちらの記事を参考にしつつ練習。
もらう DP
#include<iostream>
using namespace std;
int main() {
// 入力を受け取る
int n;
cin >> n;
int h[n], dp[n];
for (int i=0; i<n; i++) cin >> h[i];
// 最初2つを記録
dp[0] = 0;
dp[1] = abs(h[1] - h[0]);
// 最初2つをもとに、前の2つから情報をもらってdpを記録していく
for (int i=2; i<n; i++) {
dp[i] = min(
dp[i-2] + abs(h[i-2] - h[i]),
dp[i-1] + abs(h[i-1] - h[i])
);
}
cout << dp[n-1] << endl;
}
配る DP
#include<iostream>
using namespace std;
const int INF = 1 << 30
int main() {
// 入力を受け取る
int n;
cin >> n;
int h[n], dp[n];
for (int i=0; i<n; i++) {
cin >> h[i];
dp[i] = INF;
}
// 最初1つを記録
dp[0] = 0;
// 最初1つをもとに、後ろ2つのdpを記録していく
for (int i = 0; i < n; i++) {
dp[i+1] = min(dp[i+1], dp[i] + abs(h[i+1]- h[i]));
dp[i+2] = min(dp[i+2], dp[i] + abs(h[i+2]- h[i]));
}
cout << dp[n-1] << endl;
}
メモ化再帰
#include<iostream>
#include<cmath>
using namespace std;
const int INF = 1 << 30;
int h[100000];
int dp[100000];
// i番目の足場に行くまでの最小コストを返す関数
int rec(int i) {
// メモされていたらメモされた値を返す
if (dp[i] < INF) return dp[i];
// i = 0 なら明示的に値を返す
if (i == 0) return 0;
// 再帰的にrecを呼び出す
int ret = min(
rec(i-1) + abs(h[i] - h[i-1]),
i != 1 ? rec(i-2) + abs(h[i] - h[i-2]) : INF
);
// 結果をメモする
dp[i] = ret;
return ret;
}
int main() {
// 入力を受け取る
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> h[i];
// メモを初期化する
dp[i] = INF;
}
cout << rec(n-1) << endl;
}
学んだこと
- もらう DP, 配る DP, メモ化再帰という方法を整理して理解できた。
- 定数の宣言は
#define
ではなくconst
をつかうのがベター(define が文字列の埋め込みであるがゆえのトラブルを避けられる)。 - RuntimeError で疑うべきは、ゼロ除算、無限再帰、配列の範囲外アクセス。
- 無限再帰を疑ってたら、配列固定長の指定誤りという凡ミスによる範囲外アクセス=segmentation error だった…。