【三分】AcWing 3666. 士兵排阵
前言
对于一个在定义域上极值点的函数,可以求取该函数在定义域上的极值和最值。对于一个凸函数,其极值点对应的函数值就是极小值;对于一个凹函数,其极值点对应的函数就是极大值。且函数若在定义域内有且仅有一个极值点,那么极值点也是最值点。函数的最值,要么出现在极值点,要么出现在定义域上的端点。
对于三分法,其几何意义就是求一个凸函数/凹函数上的极值(同时也是最值,因为定义域上最多仅有一个极值点)。
题目
https://www.acwing.com/problem/content/3669/
题解
加法原理:如果做一件事情有两种方法,且这两种方法是互斥的,那么两种方法的方案数相加,就是做这件事的方案数。
将一个士兵从 \((x_i, y_i)\) 移动到 \((x_j, y_j)\) 的移动距离为 \(abs(x_i - x_j) + abs(y_i - y_j)\),亦即横纵坐标对答案的贡献是相互独立的。既然横纵坐标对答案的贡献是相互独立的,只需要分别使得横坐标移动距离之和与纵坐标移动距离之和最小化,两者之和就是最优解。
题意目标是使得全部士兵排成一行(且士兵相邻),那么也就是排阵完成后所有的士兵的纵坐标均相等,横坐标与相邻士兵横坐标差 \(1\)。
对于纵坐标,必定存在一个最优解 \(y\) 使得纵坐标移动距离之和最小化,移动到大于或小于 \(y\) 的纵坐标的移动距离之和必然大于等于最小化的纵坐标移动距离之和。此时类似于一个离散化的凹函数,符合三分法使用场景。
对于横坐标,不妨举例进行分析:
\((2, y_0), (1, y_1), (0, y_2)\)
不妨假设移动后的目标位置为 \((0, y), (1, y), (2, y)\),那么横坐标移动距离之和为 \(sum_x = abs(2 - 0) + abs(1 - 1) + abs(0 - 2) = 2 + 0 + 2 = 4\)
\((0, y_2), (2, y_0), (1, y_1)\)
不妨假设移动后的目标位置为 \((0, y), (1, y), (2, y)\),那么横坐标移动距离之和为 \(sum_x = abs(0 - 0) + abs(1 - 1) + abs(2 - 2) = 0\)
由上述案例容易看出,若移动目标分别为 \((x_2, y), (x_3, y),其中x_2 < x_3\) 的两个坐标 \((x_0, y_0), (x_1, y_1)\) 满足:\(x_0 < x_1 且 x_2 > x_1 且 x_3 < x_0\),此时会额外开销掉 \(2 * (x_3 - x_2)\) 的步数。
于是说明先对横坐标进行排序,再进行三分法可以使得横坐标移动距离之和最小化。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 7;
int n;
int x[N], y[N];
int ts(int a[], int (*func)(int [], int)) {
int l = -20000, r = 20000, res = INT_MAX;
while (l < r) {
int ml = l + (r - l) / 3;
int mr = r - (r - l) / 3;
int vl = func(a, ml);
int vr = func(a, mr);
if (vl <= vr) r = mr - 1, res = min(vl, res);
else l = ml + 1, res = min(res, vr);
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cin >> n;
for (int i = 0; i < n; ++ i) cin >> x[i] >> y[i];
sort(x, x + n);
auto calc_x = [](int a[], int m) -> int {
int res = 0;
for (int i = 0; i < n; ++ i) res += abs(m + i - a[i]);
return res;
};
auto calc_y = [](int a[], int m) -> int {
int res = 0;
for (int i = 0; i < n; ++ i) res += abs(m - a[i]);
return res;
};
cout << ts(x, calc_x) + ts(y, calc_y) << '\n';
return 0;
}
浙公网安备 33010602011771号