题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的数组a(1≤a[i]≤109)。
一条街道上有n个工厂,第i个工厂的位置是a[i]。你需要建造3个仓库,仓库的位置由你决定,必须是整数。每个工厂生产的物品要运到离其最近的仓库。
定义d(i)=第i个工厂到离其最近的仓库的距离。设M=所有d(i)的最大值。最小化M,也就是最小化最大的工厂到仓库的距离。
输入样例
5
6
1 7 7 9 9 9
6
5 4 2 1 30 60
9
14 19 37 59 1 4 4 98 73
1
2
6
3 10 1 17 15 11
输出样例
0
2
13
0
1
算法
前后缀分解+二分答案
这个题才1400,真的感觉不太容易。这种“三元组”类的题目比较容易想到的思路就是前后缀分解,先将a数组排序,枚举最左边仓库管辖的最右边的工厂i,根据中位数贪心的结论,此时最左侧仓库的位置就应该是pos=⌊a[1]+a[i]2⌋,其中⌊.⌋表示对.向下取整,工厂[1,i]到这个仓库距离的最大值就应该是a[i]−mid。枚举最右边仓库管辖的最左边工厂i,同理,最右侧仓库的位置就应该是pos=⌊a[n]+a[i]2⌋,预处理出pre和suf两个数组,其中pre[i]=a[i]−⌊a[1]+a[i]2⌋,suf[i]=a[n]−⌊a[n]+a[i]2⌋。
接下来根据题面的关键词求最大值的最小值,可以尝试二分这个答案。对于一个给定的最大距离限制limit,分为以下两种情况:
- 如果所有工厂到3个仓库的距离不超过limit,那么这个limit可能是答案,记录一下并向左搜索看能不能更小。
- 如果所有工厂到3个仓库的距离一定大于limit,说明limit太小了,往右搜索。
关键还是在于怎么检查这个limit是否满足要求,我们可以遍历中间仓库管辖的最左边工厂i∈[2,n),这时最左边的仓库管辖的工厂范围就是[1,i−1],这些工厂到最左边仓库的最大距离就是pre[i−1],首先要满足的就是pre[i−1]≤limit,满足这个条件的时候再考虑中间仓库的管辖范围。接下来要确定中间仓库管辖工厂的右边界,由于最大距离不能超过limit,所以这个工厂的位置距离a[i]不能超过a[i]+2limit,二分一下就能找到这个j。此时,最右边仓库的管辖范围左边界可以在区间k∈[i+1,j+1],只要这个区间内的suf[k]最小值不超过limit,就找到了三段符合要求的区间,limit是合法的。
注意到这里还存在一个查询区间最小值的操作,即查询suf在[i+1,j+1]内的最小值,所以用倍增做个预处理就行。
复杂度分析
时间复杂度
预处理出pre和suf需要对a排序,接下来是线性复杂度,所以时间复杂度为O(nlog2n)。预处理出suf的ST表时间复杂度也是O(nlog2n)。最后是二分答案,设值域跨度为A,check函数要遍历i∈[1,n),对于每个i,要二分得到j,然后O(1)查询[i+1,j+1]内suf的最小值,check的时间复杂度也为O(nlog2n)。
综上,整个算法的时间复杂度在于后面的二分答案,为O(log2Anlog2n)。
空间复杂度
空间瓶颈在于suf数组的ST表,pre数组和suf数组都是线性的。因此,算法的额外空间复杂度为O(nlog2n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 20, INF = 1e9;
int n, a[N];
int main() {
int T;
scanf("%d", &T);
for(int cases = 1; cases <= T; cases++) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a - 1;
int pre[n + 1], suf[n + 1], st[n + 1][M];
for(int i = 1; i <= n; i++) {
pre[i] = a[i] - (a[1] + a[i])/2;
}
for(int i = n; i >= 1; i--) {
suf[i] = a[n] - (a[i] + a[n])/2;
}
for(int j = 0; j < M; j++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
if(j == 0) {
st[i][j] = suf[i];
}else {
st[i][j] = min(st[i][j - 1], st[i + (1<<(j - 1))][j - 1]);
}
}
}
function<int(int, int)> query = [&](int l, int r) {
int k = log2(r - l + 1);
return min(st[l][k], st[r - (1<<k) + 1][k]);
};
function<bool(int)> check = [&](int limit) {
for(int i = 2; i < n; i++) {
if(pre[i - 1] <= limit) {
int j = upper_bound(a + 1, a + n + 1, a[i] + 2LL*limit) - a;
--j;
// [i+1,j+1]中是否存在suf[k]<=limit的k
if(query(i + 1, j + 1) <= limit) {
return true;
}
}
}
return false;
};
int l = 0, r = 1e9;
while(l < r) {
int mid = l + r >> 1;
if(check(mid)) {
r = mid;
}else {
l = mid + 1;
}
}
printf("%d\n", n <= 3? 0: r);
}
return 0;
}