題意
https://codeforces.com/problemset/problem/51/C
水平軸上有個房子,建3個基站,使得每個基站的直徑都是最小且能覆蓋所有房子。房子座標都是整數,且,輸出基站半徑和三個基站的中心座標,保留6位小數。
算法一:枚舉
中心位置未知、直徑長度未知,似乎有兩個變量需要二分搜索,且位置可能是小數,無從下手!不過,由於房子座標都是整數,因此最小直徑肯定也是整數,這樣就無需枚舉中心位置,只有一個量未知,我們可以試着寫出枚舉的程序,三條直徑,我們枚舉兩條,剩下一條只需check即可:
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
check(i, j);
}
}
可以看出是這兩條直徑的右端點:第一條,第二條,第三條,check()是三條中選一條最小的。然而根據的範圍必然會超時。
實際上,當第一個端點確定後,後面一個端點也就確定了:即後半段的中間位置便是第二條直徑的右端點,這樣可以減少枚舉量
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這個端點。程序如下:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
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<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
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;
}