Solution to CF1840D Wooden Toy Festival

发布时间 2023-07-24 18:33:17作者: escap1st

Statement

\(T\) 组评测,每组数据给定长度 \(n\) 与长度为 \(n\) 的序列 \(a\),你需要选三个数 \(x,y,z\),输出可得到的最小的 \(\max\{\min\{|a_i-x|,|a_i-y|,|a_i-z|\}\}\)

Solution

如果只要我们选一个数,显然我们要选中位数。

如果只要我们选两个数,排序后将序列下标分为 \([0, \mathrm{index})\)\([\mathrm{index}, n)\),分别选两个区间的中位数。

问题是要选三个数,不能朴素地枚举分界的下标。

发现对于某个答案的值,比它大的答案也是存在的,即答案在值域上连续,且有单调性。

二分答案 \(s\),考虑对一个 \(s\),从小到大覆盖整个序列。

保证 \(a\) 有序后,第一次选择 \(a_0 + s\) 最大能够覆盖到 \([a_0, a_0 + 2s]\)

假设 \(a_i\) 是第一个大于 \(a_0 + 2s\) 的数,第二次选择 \(a_i + s\) 最大能够覆盖到 \([a_i, a_i + 2s]\)

最后假设 \(a_j\) 是第一个大于 \(a_i + 2s\) 的数,剩下的区间即为 \([a_j, a_{n-1}]\),判断能否被选完(\(a_{n-1} - a_j + 1 \le 2s\) 是否成立)即可。

不难证明该策略最优,因此我们能在 \(\mathcal{O}(\log^2{(\max a - \min a)})\) 的时间内处理单个询问。

Code

#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int& i : a) std::cin >> i;
	std::sort(a.begin(), a.end());

	int l = 0, r = a[n - 1] - a[0];
	while (l < r) {
		int mid = (l + r) / 2;
		int pos = std::upper_bound(a.begin(), a.end(), a[0] + 2 * mid) - a.begin();
        pos = std::upper_bound(a.begin() + pos, a.end(), a[pos] + 2 * mid) - a.begin();
        if (pos == n || a[n - 1] - a[pos] <= 2 * mid) {
            r = mid;
        } else {
            l = mid + 1;
        }
	}
	std::cout << l << '\n';
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int tt;
	std::cin >> tt;
	while (tt--) solve();
	return 0;
}