problem
網格圖上給定 \(n\) 個整點 \((x_i,y_i)\),你初始在 \((0,0)\),每次只能向右或者向上走一格。你在任意位置時可以給任意若干個點打標記,假設當前位置爲 \((X,Y)\),每次給第 \(i\) 個點打標記的代價爲 \(\max(|X-x_i|,|Y-y_i|)\)。
最小化給所有點打標記的代價和。
\(n\le 8\times 10^5\),\(0\le x_i,y_i\le 10^9\)。
sol
首先對於該題,有如下引理:假設通過點 \((x_i,y_i)\) 的直線 \(y=-x+c\),則最小代價爲你走的路線與該直線相交時的代價。這個引理很顯然。
接下來我們把平面旋轉 \(45^{\circ}\),讓座標點 \((x,y)\rightarrow (x',y')=(x+y,x-y)\),這樣對於 \(x\) 相同的點,對應的直線是同一條。
設 \(f_{x,y}\) 表示在轉換後的圖上,走到 \((x,y)\) 處最小代價。假設前面橫座標最大的位置爲 \(x=a\),則 \(f_x\leftarrow f_a\),具體來說,有
其中 \(w(x,y)\) 表示 \(\sum_{x_i'=x}|y_i'-y|\)。這樣 DP
的複雜度爲 \(\mathcal O(n\cdot 10^9)\),考慮優化。
首先,\(w(x,y)\) 關於 \(y\) 是凸的,對於 \(f_{a,b}\) 關於 \(b\) 如果是凸的,\(\min\{f_{a,b}\}\) 也是凸的。所以維護凸函數即可。對於 \(\min\{a,b\}\),相當於在凸函數左半邊向左平移 \(x-a\),右半部分向右偏移 \(x-a\),這個偏移可以記錄到全局中。所有操作用堆維護,由此題性質,最優解從凸函數最小值轉移。複雜度 \(\mathcal O(n\log n)\)。
Code
#include <bits/stdc++.h>
typedef long long ll;
const int N = 8e5 + 5;
int n;
struct point { int x, y; } a[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x, y; scanf("%d%d", &x, &y);
a[i] = {x + y, x - y};
}
std::sort(a + 1, a + n + 1, [](point a, point b) { return a.x < b.x; });
std::priority_queue<int> pa, pb;
ll ans = 0; pa.push(0), pb.push(0);
for (int i = 1; i <= n; i++) {
ll x = a[i].x, y = a[i].y;
ans += std::max({pa.top() - x - y, y + pb.top() - x, 0ll});
if (-pb.top() > y - x)
pa.push(y + x), pa.push(y + x), pb.push(-(pa.top() - 2 * x)), pa.pop();
else
pb.push(-(y - x)), pb.push(-(y - x)), pa.push(-pb.top() + 2 * x), pb.pop();
}
ans /= 2;
printf("%lld\n", ans);
return 0;
}