SRM 281 DIV1 Hard Equidistance
問題
数列initialが与えられる。
これの各々を移動させて、等差数列の並び替えにしたい。ただしそれぞれの数値は重複があってはいけない。
移動させるのに移動させた分だけコストがかかる。
コストを最小化せよ
- 2 <= |initial| <= 50
- -2*10^9 <= initial[i] <= 2*10^9
解答
Editorialを見た。
まず、数列をソートできる。
そうすると、等差数列の順序はそのままの順序でよくなる。
次に、どれかの数1つはそのままの位置で良い。
そうすると、コストは等差の関数(d => Σ |v[fixed] + d * (j - fixed) - v[j]|)になる。
そしてこの関数は凸である。何故ならば、Sumの各項は凸で、任意の凸関数の非負係数の線形結合は凸になるからである。
凸ならば、二分探索で答えが出る
コメント
まずソートするというのは普通に考えよう。それによって順序が決めることのできることもある。
「任意の凸関数の非負係数の線形結合は凸」は当たり前だけれども、その形に注意して見てみよう。
コード
#include <vector> #include <algorithm> #define rep(i,n) for(int (i)=0;(i)<(int)(n);++(i)) #define all(o) (o).begin(), (o).end() #define INFL 0x3f3f3f3f3f3f3f3fLL using namespace std; typedef vector<int> vi; typedef long long ll; struct Equidistance { vi v; int n; ll effort(int i, ll d) { ll r = 0; rep(j, n) r += abs((ll)v[i] + d * (j - i) - v[j]); return r; } long long minimumEffort(vector <int> initial) { v = initial, n = v.size(), sort(all(v)); ll r = INFL; rep(i, n) { ll l = 1, u = 4000000001LL; while(u-l>1) { ll mid = (l+u)/2; (effort(i, mid) - effort(i, mid-1) <= 0 ? l : u) = mid; } r = min(r, effort(i, l)); } return r; } };