题目描述
难度分:1600
输入T(≤103)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤105)和长为n的数组x(0≤x[i]≤108)和长为n的数组t(0≤t[i]≤108)。
你需要计算一个x0,使得t[i]+|x[i]−x0|的最大值最小,输出x0,x0可以是浮点数。
输入样例
7
1
0
3
2
3 1
0 0
2
1 4
0 0
3
1 2 3
0 0 0
3
1 2 3
4 1 2
3
3 3 3
5 3 3
6
5 4 7 2 10 4
3 2 5 1 4 6
输出样例
0
2
2.5
2
1
3
6
算法
二分答案
这个题要把最大值最小化,很容易想到二分答案,而x0还可以是浮点数,所以自然而然的想到实数二分。对于一个给定的limit,要想对于任何i∈[1,n]都满足t[i]+|x[i]−x0|≤limit,需要求出所有不等式解集的交集。将绝对值符号拆开,得到每个不等式的解集为[x[i]+t[i]−limit,x[i]−t[i]+limit],且t[i]≤limit必须成立,O(n)求每个不等式解集的交集。
- 如果这个交集不为空,说明有解。直接把交集[left,right]的左端点left赋值给x0,并往左搜索limit看这个最大值能不能更小。
- 否则无解,放宽最大值的限制,往右搜索limit。
由于实数二分有可能一直达不到精度要求,因此我加了个最多迭代100次的限制。
复杂度分析
时间复杂度
实数二分的二分次数为max(100,log2A),每次check的时间复杂度为O(n),因此总的时间复杂度为O(max(100,log2A)n)。
空间复杂度
求解过程中仅使用了有限几个变量,额外空间复杂度为O(1)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
const double eps = 1e-8;
int t, n, a[N], b[N];
double x0;
bool check(double limit) {
double left = -1e9, right = 1e9;
for(int i = 1; i <= n; i++) {
if(b[i] <= limit) {
left = max(left, a[i] + b[i] - limit);
right = min(right, a[i] - b[i] + limit);
}else {
return false;
}
}
if(left <= right) {
x0 = left;
return true;
}else {
return false;
}
}
void solve() {
double l = 0, r = 1e9;
for(int i = 0; i < 100 && r - l > eps; i++) {
double mid = (l + r)*0.5;
if(check(mid)) {
r = mid;
}else {
l = mid;
}
}
printf("%.8lf\n", x0);
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
}
solve();
}
return 0;
}