Codeforces 51C Three Base Stations

題意

https://codeforces.com/problemset/problem/51/C

xx水平軸上有n(2×105)n(\le 2 \times 10^5)個房子,建3個基站,使得每個基站的直徑都是dd最小且能覆蓋所有房子。房子座標都是整數,且109\le 10^9,輸出基站半徑和三個基站的中心座標,保留6位小數。

算法一:枚舉

中心位置未知、直徑長度未知,似乎有兩個變量需要二分搜索,且位置可能是小數,無從下手!不過,由於房子座標都是整數,因此最小直徑肯定也是整數,這樣就無需枚舉中心位置,只有一個量未知,我們可以試着寫出枚舉的程序,三條直徑,我們枚舉兩條,剩下一條只需check即可:

for (int i = 1; i <= n; i++)
{
   for (int j = i; j <= n; j++)
   {
	   check(i, j);
   }
}

i,ji,j可以看出是這兩條直徑的右端點:第一條1..i1..i,第二條i+1..ji+1..j,第三條j+1..nj+1..n,check()是三條中選一條最小的。然而根據nn的範圍必然會超時。

實際上,當第一個端點確定後,後面一個端點也就確定了:即後半段的中間位置便是第二條直徑的右端點,這樣可以減少枚舉量jj

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);
}

不過這樣可能會漏掉更小的答案,因爲第一條直徑確定後,答案可能在後半段的中間位置,因此還需checkj1j-1這個端點。程序如下:

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章