https://codeforces.com/problemset/problem/51/C
x x x水平轴上有 n ( ≤ 2 × 1 0 5 ) n(\le 2 \times 10^5) n(≤2×105)个房子,建3个基站,使得每个基站的直径都是 d d d最小且能覆盖所有房子。房子坐标都是整数,且 ≤ 1 0 9 \le 10^9 ≤109,输出基站半径和三个基站的中心坐标,保留6位小数。
中心位置未知、直径长度未知,似乎有两个变量需要二分搜索,且位置可能是小数,无从下手!不过,由于房子坐标都是整数,因此最小直径肯定也是整数,这样就无需枚举中心位置,只有一个量未知,我们可以试着写出枚举的程序,三条直径,我们枚举两条,剩下一条只需check即可:
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
check(i, j);
}
}
i , j i,j i,j可以看出是这两条直径的右端点:第一条 1.. i 1..i 1..i,第二条 i + 1.. j i+1..j i+1..j,第三条 j + 1.. n j+1..n j+1..n,check()是三条中选一条最小的。然而根据 n n n的范围必然会超时。
实际上,当第一个端点确定后,后面一个端点也就确定了:即后半段的中间位置便是第二条直径的右端点,这样可以减少枚举量 j j j
for (int i = 1, j = 1; i <= n; i++)
{
while (j < n && a[n] - a[j+1] > a[j] - a[i+1])
j++;
check(i, j);
}
不过这样可能会漏掉更小的答案,因为第一条直径确定后,答案可能在后半段的中间位置,因此还需check j − 1 j-1 j−1这个端点。程序如下:
#include
#include
#include
#include
using namespace std;
const int maxn = 2e5 + 3;
int a[maxn], n;
double pa, pb, pc, ans = 1e9;
void check(int x, int y)
{
if (y < 1) y = 1;
int z = a[x] - a[1];
z = max(a[y] - a[min(x+1, n)], z);
z = max(a[n] - a[min(y+1, n)], z);
if (z < ans)
{
ans = z;
pa = a[x] + a[1];
pb = a[y] + a[min(x+1, n)];
pc = a[n] + a[min(y+1, n)];
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
sort(a + 1, a + n + 1);
for (int i = 1, j = 1; i <= n; i++)
{
while (j < n && a[n] - a[j+1] > a[j] - a[i+1])
j++;
check(i, j-1);
check(i, j);
}
printf("%.6lf\n%.6lf %.6lf %.6lf\n", ans / 2.0, pa / 2.0, pb / 2.0, pc / 2.0);
return 0;
}
二分一个直径,然后检查是否能全覆盖。bound函数看上去似乎是“二分套二分”,实际不是。右端点在哪也可以像枚举一样线性查找,不过二分会更快。
#include
#include
#include
#include
using namespace std;
const int maxn = 2e5 + 3;
int a[maxn], n, pos[3];
int bound(int d)
{
int i = 1, j = n;
while (i <= j)
{
int mid = (i+j) >> 1;
if (a[mid] <= d)
i = mid + 1;
else
j = mid - 1;
}
return i; // 返回覆盖后,右端的下一个断点
}
bool check(int d)
{
int tmp = 1;
for (int i = 0; i < 3; i++)
{
tmp = bound(a[tmp] + d);
pos[i] = tmp;
if (tmp >= n+1) // 因为bound返回的是下一个端点,因此这里的条件是n+1
return true;
}
return false;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
sort(a + 1, a + n + 1);
int i = 0, j = a[n];
while (i < j)
{
int mid = (i+j) >> 1;
if (check(mid))
j = mid;
else
i = mid + 1;
}
check(i); // 跳出二分时,最后一次可以能若在esle分支,这时pos[]里的数据可能是上一次的错误数据
double ans = i / 2.0;
double pa = (a[1] + a[pos[0] - 1]) / 2.0;
double pb = (a[pos[0]] + a[pos[1] - 1]) / 2.0;
double pc = (a[pos[1]] + a[pos[2] - 1]) / 2.0;
printf("%.6lf\n%.6lf %.6lf %.6lf\n", ans, pa, pb, pc);
return 0;
}