D - フェンスの塗装 / Painting the Fence Editorial by admin Editorial for printing
gpt-6-astra-medium概要
隣り合う板の色が異なるように塗るときの、合計コストの最小値を求めます。動的計画法の遷移を「最小値と、別の色に対応する第2最小値」で高速化すると、\(O(NK)\) 時間で解けます。
考察
最後に使った色を状態にする
板を左から順に塗ることを考えます。次の板に使える色は、直前の板の色によって決まるため、次の状態を定義します。
\(dp[i][j]\):板 \(1\) から板 \(i\) までを条件を満たすように塗り、板 \(i\) を色 \(j\) にしたときの最小コスト
最初の板には隣接条件がないので、
\(dp[1][j] = C_{1,j}\)
です。板 \(i\) を色 \(j\) にする場合、板 \(i-1\) の色は \(j\) 以外でなければならないため、
\(dp[i][j] = C_{i,j} + \min_{k \ne j} dp[i-1][k]\)
となります。
答えは、最後の板の色を自由に選んだ
\(\min_{1 \le j \le K} dp[N][j]\)
です。
ただし、各状態で \(j\) 以外の色をすべて調べると、時間計算量は \(O(NK^2)\) となります。\(NK \le 10^6\) でも \(K\) が大きい場合には間に合いません。
最小値と第2最小値で高速化する
必要なのは「特定の色を除いた最小値」です。そこで、前の板について次の3つを保持します。
minimum:全色の中での最小コストminimumColor:その最小コストを達成する色の1つsecondMinimum:minimumColor以外の色の中での最小コスト
板 \(i\) を色 \(j\) にするとき、前の板までの最小コストは次のように選べます。
- \(j \ne \text{minimumColor}\) なら、
minimumを使う。 - \(j = \text{minimumColor}\) なら、同じ色を使えないので
secondMinimumを使う。
これにより、各状態を \(O(1)\) 時間で計算できます。
例えば、前の板までのコストが色ごとに \([4,4,9]\) なら、最小値を達成する色として色 \(1\) を選んだ場合、第2最小値は色 \(2\) の \(4\) です。
ここでの第2最小値は「最小値より大きい値」ではありません。別の色に対応する値なら、最小値と同じでもよいことが重要です。
アルゴリズム
各板について、前の板の3つの情報から、現在の板の3つの情報を求めます。
- 初期値を次のように設定します。
minimum = 0secondMinimum = 0minimumColor = -1
コードでは色を \(0\) から \(K-1\) で扱うため、最初の板ではどの色も minimumColor と一致しません。その結果、すべての色について塗装コストだけが計算されます。
現在の板の情報を初期化します。
nextMinimum = INFnextSecondMinimum = INFnextMinimumColor = -1
各色 \(j\) のコストを読み込み、現在の板をその色で塗る最小コスト
valueを求めます。- \(j = \text{minimumColor}\) なら、
value = cost + secondMinimum - それ以外なら、
value = cost + minimum
- \(j = \text{minimumColor}\) なら、
valueを使って、現在の板の最小値と第2最小値を更新します。value < nextMinimumの場合:- これまでの最小値を第2最小値に移す。
- 最小値を
valueに更新し、その色を記録する。
- それ以外で
value < nextSecondMinimumの場合:- 第2最小値を
valueに更新する。
- 第2最小値を
全色を処理したら、現在の板の情報を次の板の計算用に引き継ぎます。
すべての板を処理した後、minimum を出力します。
各色の value は動的計画法の遷移式そのものであり、更新によって次の板に必要な最小値・第2最小値が正しく保持されるため、最終的に最小コストが得られます。
計算量
- 時間計算量: \(O(NK)\)
- 空間計算量: \(O(1)\)
各板の各色を1回ずつ処理します。入力されたコストはその場で計算に使うため、コスト表や DP 配列を保存する必要はありません。
実装のポイント
合計コストには64ビット整数を使います。
合計は最大で \(N \times 10^9 \le 10^{14}\) となるため、C++ ではlong longが必要です。コードのINF = (1LL << 62)は、これより十分大きい値です。最小値と同じ値も第2最小値の候補にします。
value == nextMinimumの場合も、別の色として第2最小値を更新できる必要があります。コードでは最初のifに入らず、続くelse ifで処理されます。前の板の情報を途中で上書きしないようにします。
現在の板の全色を計算する間は、前の板の情報を固定して使います。そのため、更新用にnextMinimumなどの変数を用意しています。\(N=1\) も同じ処理で扱えます。
初期化によって最初の板のvalueは入力コストそのものになり、その最小値が答えになります。ソースコード
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, K;
cin >> N >> K;
constexpr long long INF = (1LL << 62);
long long minimum = 0, secondMinimum = 0;
int minimumColor = -1;
for (int i = 0; i < N; ++i) {
long long nextMinimum = INF, nextSecondMinimum = INF;
int nextMinimumColor = -1;
for (int j = 0; j < K; ++j) {
long long cost;
cin >> cost;
long long value = cost +
(j == minimumColor ? secondMinimum : minimum);
if (value < nextMinimum) {
nextSecondMinimum = nextMinimum;
nextMinimum = value;
nextMinimumColor = j;
} else if (value < nextSecondMinimum) {
nextSecondMinimum = value;
}
}
minimum = nextMinimum;
secondMinimum = nextSecondMinimum;
minimumColor = nextMinimumColor;
}
cout << minimum << '\n';
return 0;
}
この解説は gpt-6-astra-medium によって生成されました。
posted:
last update: