AtCoder Beginner Contest 217 - ABC 217 H - Snuketoon の解法です. slope trick を使う公式解説そのままです.
問題概要
変数 P, S がある.時刻0には P = S = 0.
時刻1, 2, 3, ... に P に +1/-1/0 のいずれかを加えることができる.
列 ((T_i, D_i, X_i) : i ∈ [1, N]) が与えられる.
T_i は単調増加する正の整数,D_i は 0または1,X_i は整数.
時刻 T_i (Pの値の変化後) において,S に dmg(P, i) を加える.ただし,
dmg(P, i) = max(0, X_i - P) (D_i = 0 のとき)
max(0, P - X_i) (D_i = 1 のとき)
S を最小化するように P を変化させよ.
制約: N <= 2e5, 1 <= T_1 < T_2 < ... < T_N <= 1e9, | X_i | <= 1e9
解法
次の自然なDPが考えられますが,状態空間が大きすぎて間に合いません.
- 定義: dp[i][p] := 時刻 T_i において P = p の時の S の最小値
- 遷移: dp[i][p] := dmg(p, i) + min { dp[i-1][q] : | q - p | <= T_i - T_{i+1} }
関数 f_i(p) := dp[i][p] が,各 i に対して, 折れ線凸関数になることを利用します. 折れ線凸関数は,次の3つで特徴付けられます.
- 最小値 min(f)
- 傾きが 0→1, 1→2, … と変化する点のリスト pos(f)
- 傾きが -1→0, -2→-1, … と変化する点のリスト neg(f)
今回の問題の場合,次を実施すれば良いです.
- T_{i-1} から T_i までの時間経過の間に,t := T_i - T_{i-1} だけ 移動できます.これに対応して,pos(f) の各点が t だけ右に移動し, neg(f) の各点が t だけ左に移動します.
- dmg(p, i) が加えられることに対応して,X_i を pos(f) なり neg(f) なり
の適切な位置に挿入します.さらに,
- D_i = 1 で neg(f) に X_i を挿入した場合には,neg(f) の先頭を pos(f) の先頭に移します.
- D_i = 0 で pos(f) に X_i を挿入した場合には,pos(f) の先頭を neg(f) の先頭移します.
- これらの場合には,min(f) も適切に更新します.たとえば,pos(f) が {x0, x1, … } だったときに,D_i = 0 で pos(f) に q が挿入されたとすると, x0 を neg(f) に移すとともに,min(f) に q - x0 を加えます.
第1点の移動を愚直に行うと間に合いませんので,移動距離を覚えておくだけにして, 先頭を移す操作や,X_i を挿入する操作の際には,移動距離を考慮に入れます.
f_N の最小値が求める答です.
実装は,pos(f) と neg(f) に優先度キューを用いました. 時刻 0 における f は,f(0) = 0 で,p != 0 のときには,f(p) = ∞ になっていてほしいです. これは,pos(f) と neg(f) の初期値が (0,0,0,…) であることを意味します. しかし,実際にたくさんの0を入れてしまっては効率が悪いので, 0 はpopしないことで対処しました.
ACコード
#include <bits/stdc++.h>
#include <cassert>
typedef long long int ll;
using namespace std;
int main(/* int argc, char *argv[] */) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout << setprecision(20);
ll N; cin >> N;
ll minf = 0;
priority_queue<ll, vector<ll>, greater<ll>> posf;
priority_queue<ll> negf;
ll shift = 0;
auto push_pos = [&](ll x) -> void { if (x < shift) posf.push(x - shift); };
auto push_neg = [&](ll x) -> void { negf.push(x + shift); };
auto top_pos = [&]() -> ll { return posf.top() + shift; };
auto top_neg = [&]() -> ll { return negf.top() - shift; };
auto pop_pos = [&]() -> void { if (posf.top() != 0) posf.pop(); };
auto pop_neg = [&]() -> void { if (negf.top() != 0) negf.pop(); };
posf.push(0);
negf.push(0);
for ( ; N > 0; N--) {
ll t, d, x; cin >> t >> d >> x;
shift = t;
if (d == 0) {
ll y = top_pos();
if (x <= y) {
push_neg(x);
}else {
minf += x - y;
push_pos(x);
pop_pos();
push_neg(y);
}
}else if (d == 1) {
ll y = top_neg();
if (y <= x) {
push_pos(x);
}else {
minf += y - x;
push_neg(x);
pop_neg();
push_pos(y);
}
}
}
cout << minf << endl;
return 0;
}