難易度評価

難易度を1〜10段階で評価できます。投票はあとから変更・取り消しできます。

難易度(投票) 未投票 (0票)

投票の分布

まだ投票はありません。

  1. Lv.10票
  2. Lv.20票
  3. Lv.30票
  4. Lv.40票
  5. Lv.50票
  6. Lv.60票
  7. Lv.70票
  8. Lv.80票
  9. Lv.90票
  10. Lv.100票

難易度の目安を見る ↗

ログインして難易度を投票

外部サイトへ移動します

リンク先の URL を確認してから開いてください。

別タブで開く

Safe Bridge

作成者 frugq

難易度(作成者設定) Lv.5

難易度(投票) 未投票 (0票)

実行時間制限
2 秒
メモリ制限
512 MiB

解説

危険度がa以下の橋にするための最小コストをf(a)f(a)とします。f(a)f(a)はdpにより計算することができます。

  • X0=0,Y0=0,XN+1=L,YN+1=CX_0=0, Y_0=0, X_{N+1}=L, Y_{N+1}=Cとする
  • dpidp_iをii番目の柱を建設するとき、それまでの柱のコストの合計の最小値と定義する
  • dp0=0, dpi=∞(i>0)dp_0=0,\ dp_i = \infty(i>0) で初期化する
  • dpi=dp_i=(Xi−a≤Xj<XiX_i-a \leq X_j < X_iを満たすjjについて、dpjdp_jの最小値+YiY_i) で更新する
  • dpN+1dp_{N+1}がf(a)f(a)である

ここで、区間の最小値を取得する必要がありますが、セグメント木やスライド最小値で求められます。

f(a)f(a)は広義単調減少なので、二分探索によって問題を解くことができます。全体の計算量はセグメント木解法ならO(Nlog⁡Nlog⁡L)O(N\log N \log L)、スライド最小値解法ならO(Nlog⁡L)O(N \log L)です。

実装例(segtree)
#include <bits/stdc++.h>
#include <atcoder/segtree>
using namespace std;
using namespace atcoder;
using ll = long long;

const ll inf = 1LL << 60;
using S = ll;

S op(S a, S b) {
    return min(a, b);
}

S e() {
    return inf;
}

int main() {
    ll n, l, c;
    cin >> n >> l >> c;
    vector<ll> x(n + 2, 0), y(n + 2, 0);
    for(int i = 1; i <= n; ++i) cin >> x.at(i);
    for(int i = 1; i <= n; ++i) cin >> y.at(i);
    x.at(n + 1) = l;
    x.push_back(inf);

    auto calcCost = [&](ll maxDist){
        int leftIdx = 0;
        segtree<S, op, e> dp(n + 2);
        dp.set(0, 0);
        for(int i = 1; i <= n + 1; ++i){
            while(x.at(leftIdx) < x.at(i) - maxDist) leftIdx++;
            if(leftIdx < i){
                ll tmp = dp.prod(leftIdx, i) + y.at(i);
                dp.set(i, tmp);
            }
        }
        return dp.get(n + 1);
    };

    ll ng = -1, ok = l + 1;
    while(ok - ng > 1){
        ll mid = (ok + ng) / 2;
        if(calcCost(mid) <= c){
            ok = mid;
        }else{
            ng = mid;
        }
    }
    cout << ok << endl;
    return 0;
}
実装例(スライド最小値)
#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using P = pair<ll, ll>;
const ll inf = 1LL << 60;

int main() {
    ll N, L, C;
    cin >> N >> L >> C;
    vector<ll> X(N), Y(N);
    for(int i = 0; i < N; ++i) cin >> X.at(i);
    for(int i = 0; i < N; ++i) cin >> Y.at(i);

    X.push_back(L);
    Y.push_back(0);

    //危険度がmaxDist以下の橋の最小コストを計算
    auto calcCost = [&](ll maxDist){
        deque<P> que;//(pos, cost)、スライド最小値(Sliding Window Minimum)、単調増加列のみもつ
        que.push_back(P(0, 0));
        vector<ll> dp(N + 1, inf);
        for(int i = 0; i <= N; ++i){
            //dequeの先頭削除
            while(!que.empty() && X.at(i) - que.front().first > maxDist){
                que.pop_front();
            }
            if(!que.empty()){
                dp.at(i) = min(dp.at(i), que.front().second + Y.at(i));
            }
            //dequeに追加
            while(!que.empty() && que.back().second >= dp.at(i)){
                que.pop_back();
            }
            que.push_back(P(X.at(i), dp.at(i)));
        }
        return dp.at(N);
    };

    //二分探索
    ll ng = -1, ok = L + 1;
    while(ok - ng > 1){
        ll mid = (ok + ng) / 2;
        if(calcCost(mid) <= C){
            ok = mid;
        }else{
            ng = mid;
        }
    }
    cout << ok << endl;
    return 0;
}

ユーザー解説

ユーザー解説を書く

ユーザー解説はまだありません。