昔は苦手だったけどこれはだいぶ慣れてきた。
https://atcoder.jp/contests/dp/tasks/dp_z
問題
N個の足場があり、それぞれの高さはH[i]である。
i番の足場から、より番号の大きいj番の足場に移動するときのコストは、定数値Cを用いて(H[i]-H[j])^2+Cと計算される。
1番の足場からN番に移る最小コストを求めよ。
解法
Convex Hull Trickで解く。
dp(i) := i番の足場に至る最小コスト
とすると、
dp(j) = min(dp(i) + (H[i]-H[j])^2+C)だが、dp(i) + (H[i]-H[j])^2+CはH[j]^2 - 2*H[i]*H[j] + H[i]^2+Cと変形し、H[j]は固定と考えるとxの係数が2*H[i]であるような多数の1次式の最小値を取る問題とみなせる。
あとはConvex Hull Trickを使っていこう。
以下類題。
yukicoder : No.703 ゴミ拾い Easy - kmjp's blog
template<typename V> struct ConvexHull { deque<pair<V,V>> Q; V calc(pair<V,V> p, V x) { return p.first*x+p.second; } int dodo(pair<V,V> A,pair<V,V> B, pair<V,V> C) { // max or min return ((A.second-C.second)*(B.first-A.first)<=(A.second-B.second)*(C.first-A.first)); } void add(V a, V b) { // add ax+b Q.push_back({a,b}); int v; while((v=Q.size())>=3 && dodo(Q[v-3],Q[v-2],Q[v-1])) Q[v-2]=Q[v-1], Q.pop_back(); } V query(V x) { int L=-1,R=Q.size()-1; while(R-L>1) { int M=(L+R)/2; (((calc(Q[M],x)>=calc(Q[M+1],x)))?L:R)=M; } return calc(Q[R],x); } }; int N; ll C,H[202020],R[202020]; ConvexHull<ll> cht; void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>C; FOR(i,N) { cin>>H[i]; if(i) { R[i]=cht.query(H[i])+H[i]*H[i]; } cht.add(-2*H[i],H[i]*H[i]+R[i]+C); } cout<<R[N-1]<<endl; }
まとめ
ここ数年でメジャーになった競プロのテクニックってあまり思い浮かばないけど、あえて言うとCHTな印象。