けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AOJ 3054 Tunnel (RUPC 2019 day2-D)

離散量だなんて思わなかった。。。

問題へのリンク

問題概要

(略)

 N 個の長方形からなるヒストグラム (x 座標が  0 から  N の範囲に存在していて、それぞれの長方形の高さが  h_1, h_2, \dots, h_N) が与えられて、それに合わせて折れ線を最適化する。

  • 折れ線の始点は  (0, 1)
  • 正の整数  y_1, y_2, \dots, y_N があって、折れ線は  (i, y_i) を順に結んでいく。
  • 折れ線のスコアは、折れ線とヒストグラムによって形成されるような図形の面積の総和 (下図は問題文より)

折れ線のスコアの最小値を求めよ。

f:id:drken1215:20190307001724p:plain

制約

  •  2 \le N \le 150
  •  1 \le h_i \le 150

考えたこと

えーーー、連続量だと思った...連続量でちょっと解析すると、折れ線がヒストグラムの下に位置するところから開始するとき、あえて天井を突き破って (√2 - 1) 倍だけ出っぱらせるのがスコアが最小になることが示せたりする。

それはともかく、離散量だとわかれば典型的な DP で

  • dp[ i ][ h ] := 最初の i 個分のヒストグラムを見て、最後の高さが h になるような折れ線のスコアの最小値

としてあげればよい。ここで h の範囲が気になるところだが、ヒストグラムの高さの最大長から高々 √2 - 1 倍までのはみ出しまで許容すればよい。なので h <= 300 程度で十分。

#include <iostream>
#include <vector>
#include <cmath>
#include <iomanip>
using namespace std;
const double INF = 1LL<<60;

double calc(double cur, double next, double h) {
    if ((cur-h) * (next-h) >= 0) {
        return (abs(cur-h) + abs(next-h)) / 2;
    }
    else {
        double a = abs(cur - h), b = abs(next - h);
        return (a*a + b*b) / (a + b) / 2;
    }
}

int main() {
    int N; cin >> N;
    vector<double> H(N);
    for (int i = 0; i < N; ++i) cin >> H[i];

    vector<vector<double> > dp(N+1, vector<double>(300, INF));
    dp[0][1] = 0;
    for (int i = 0; i < N; ++i) {
        for (int h = 1; h < 300; ++h) {
            for (int h2 = 1; h2 < 300; ++h2) {
                dp[i+1][h2] = min(dp[i+1][h2], dp[i][h] + calc(h, h2, H[i]));
            }
        }
    }
    double res = INF;
    for (int h = 1; h < 300; ++h) res = min(res, dp[N][h]);
    cout << fixed << setprecision(10) << res << endl;
}