1. 程式人生 > 其它 >ICPC2021上海區域賽 D.Walker(二分、分類討論)

ICPC2021上海區域賽 D.Walker(二分、分類討論)

  • 題目:Walker
  • 題意:給出兩個點a、b在一條線上的位置,並給出兩個點的速度,問最少需要多少時間可以將整條線段覆蓋(a、b方向任意)
  • 解析:
    1. a或b一人獨自走完線段,兩者最短時間t1
    2. a、b相向而行,對穿整條線段,兩者最長時間t2
    3. 二分一個在a、b之間的點x,a負責區間[0, x],b負責區間[x, n]兩者所花費的最大時間t3
    4. 最後比較t1、t2、t3大小
      重點1: 第一種和第二種的情況都挺好想的,主要想總結一下第三種情況,實際上第三種情況包含了非常多的情況,然後這裡二分的思想是希望a與b完成相應區間覆蓋所花的時間儘可能接近,因為最終是要完成整條線段的覆蓋,類似於木桶效應;
      重點2: 其次,這裡的二分有點卡精度,eps需要開到1e-7,看其他大佬寫的題解發現,二分迴圈100次精度可以到達1e-30???,這樣不容易被卡精度。
  • 程式碼1:
#include<bits/stdc++.h>
using namespace std;
int t;
double n, pa, pb, va, vb;
int main()
{
    cin >> t;
    while(t --)
    {
        double res = 1000000000;
        cin >> n >> pa >> va >> pb >> vb;
        if(pa > pb) swap(pa, pb), swap(va, vb); //始終讓a在左側
        res = min( (n + min(pa, n - pa)) / va, (n + min(pb, n - pb)) / vb); //a、b獨自走完一條線段
        res = min(res, max( (n - pa) / va, pb / vb ) ); //a、b相向而行走到邊界
        // 二分列舉一個點x, a、b負責覆蓋x的左側、右側
        double l = pa, r = pb;
        while(r - l > 1e-7)
        {
            double mid = (l + r) / 2.0;
            double ta = min(pa + mid, 2 * mid - pa) / va; //先向左到起點後返回mid/先向右到mid再返回起點
            double tb = min(n + pb - 2 * mid, 2 * n - pb - mid) / vb;
            res = min(res, max(ta, tb)); //類似木桶效應
            if(ta > tb) r = mid; //縮小a的時間
            else l = mid; //增加a的時間
        }
        printf("%.6lf\n", res);
    }
    return 0;
}

  • 程式碼2(控制精度方法):
#include<bits/stdc++.h>
using namespace std;
int t;
double n, pa, pb, va, vb;
int main()
{
    cin >> t;
    while(t --)
    {
        double res = 1000000000;
        cin >> n >> pa >> va >> pb >> vb;
        if(pa > pb) swap(pa, pb), swap(va, vb); //始終讓a在左側
        res = min( (n + min(pa, n - pa)) / va, (n + min(pb, n - pb)) / vb); //a、b獨自走完一條線段
        res = min(res, max( (n - pa) / va, pb / vb ) ); //a、b相向而行走到邊界
        // 二分列舉一個點x, a、b負責覆蓋x的左側、右側
        double l = pa, r = pb;
        for(int i = 1; i <= 100; i++)
        {
            double mid = (l + r) / 2.0;
            double ta = min(pa + mid, 2 * mid - pa) / va; //先向左到起點後返回mid/先向右到mid再返回起點
            double tb = min(n + pb - 2 * mid, 2 * n - pb - mid) / vb;
            res = min(res, max(ta, tb)); //類似木桶效應
            if(ta > tb) r = mid; //縮小a的時間
            else l = mid; //增加a的時間
        }
        printf("%.6lf\n", res);
    }
    return 0;
}