[CF1534G]A New Beginning

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\),具体来说,有

\[f_{x,y}=\min\{f_{a,b}\}+w(x,y),y-(x-a)\le b\le y+(x-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;
}

posted @ 2021-06-21 09:57  AC-Evil  阅读(160)  评论(0编辑  收藏  举报