Educational DP Contest N - Slimes
問題の概要
匹のスライムが横一列に並んでいて、左から 番目のスライムの大きさは である。
隣り合う 匹のスライムを合体させて、 匹のスライムにする。
このとき、 匹のスライムの大きさを とすると、 のコストを支払う。
また、合体後の大きさは となる。
この操作を繰り返して、スライムを 匹にするまでのコストの最小値を求める。
制約
(考察 →) 実装
貪欲っぽく解けないかと思ったが、多分無理そう。(反例を考える気力がない…)
これも DP で解けるらしい。
を以下のように定義する。
- := 半開区間 のスライムに対して合体処理を行った場合の、コストの総和の最小値
正直この設定も浮かばないのだが、世の人たち定義するのうますぎない?
それはさておき、次はどう遷移させるかである。
区間 を考えているとき、この区間を で二つの区間に分割する。
分割された二つの区間は、 と となる。
この は、開区間 上を動く。
ある で分割したときの図は以下のようになる。( は 個ある。)
区間が小さくなるまで、この分割操作を繰り返していき、その値を返していく。
分割統治法っぽい (?)
図は雑すぎるので、気が向いたら書き直すかも。
正直数列っぽく書いた方が見やすい気がするが、その気力がなかった…
と に分割したものを合体させるということは、区間 で合体させたスライムと、区間 で合体させたスライムとを合体させるということである。
区間 でスライムを合体させるのに必要な最小コスト :
区間 でスライムを合体させるのに必要な最小コスト :
であり、この二つのスライムを合体させるときにかかるコストは、
:= 配列 の左から 個の要素の総和とする (累積和を取る) と、 となる。
一度訪問した区間をメモしておくと、計算量を削減できる。
計算量の見積りが分からない、多分 くらい。
ソースコードも載せておく。
#include <iostream> #include <vector> #include <algorithm> #include <limits> using namespace std; #define chmin(x, y) x = min(x, y) // 入力 int n; vector<long long> a; // dp[left][right] := 区間 [left,right) におけるコストの最小値 vector<vector<long long>> dp(405, vector<long long>(405, -1)); // スライムの大きさの累積和 long long S[405]; // メモ化再帰 long long memo_rec(int left, int right){ if(dp[left][right] != -1) return dp[left][right]; if(right - left == 1) return dp[left][right] = a[left]; if(right - left < 1) return 0; // これは必要なわけではない long long res = numeric_limits<long long>::max(); for(int pos = left + 1; pos < right; ++pos){ long long tmp = 0; if(pos - left > 1) tmp += memo_rec(left, pos) + S[pos] - S[left]; else tmp += memo_rec(left, pos); if(right - pos > 1) tmp += memo_rec(pos, right) + S[right] - S[pos]; else tmp += memo_rec(pos, right); chmin(res, tmp); } return dp[left][right] = res; } int main(){ cin >> n; a.resize(n); for(int i = 0; i < n; ++i){ cin >> a[i]; S[i+1] = S[i] + a[i]; } cout << memo_rec(0, n) << endl; }