codeforces #8D Two Friends (二分答案+計算幾何)
阿新 • • 發佈:2018-12-23
題目連結;
題意:
有兩個人Alan 和Bob ,他們現在都在A 點,現在Bob 想去B 點,Alan 想先到C 點再去B 點。
要求Alan 走過的長度不能超過最短路長度+t1 ,Bob 走過的長度不能超過最短路長度+t2 ,求兩人在一起最多走多久(分開後再匯合不算一起走)?
題解:
設Alan 最多走L1 ,Bob 最多走L2 ,注意還要加上t1 和t2 這兩個差值。
首先如果Bob 能陪伴Alan 全程(即L2≥Distance(A,C)+Distance(C,B)) ,那麼答案顯然為min(L1,L2) 。此時他們一定是在Alan 到達C 之前分開的
否則兩人分離時Bob 一定還沒有經過C 點 ,這時顯然不比一起回家更優。
容易發現答案是單調的,我們不妨二分答案x ,即Alan 和Bob 走距離為x 的相同路線後分開。
不妨設分離點為P ,當前二分到mid ,那麼:
Distance(P,A)≤mid
Distance(P,C)≤L1−Distance(B,C)−mid
即:
設分離點為P ,那麼點P 必須滿足一下三個條件:
P 必須在以A 為圓心半徑為x 的圓內,因為他們走的公共距離為x
P 必須在以B 為圓心半徑為L2−x 的圓內,為了讓Bob 在分開之後能及時返回B 點
P 必選在以C 為圓心半徑為L1−x−B C 的圓內,因為Alan 在到達C 之後還要徑直走回B 點。
所以如果三個圓相交,那麼一定存在這樣的點P 。
所以容易發現每個不等式中P 的範圍都是一個圓 。
因此我們只需要判斷三個圓是否有公共部分即可 。
判斷三個圓是否相交:
三個圓兩兩相交是必要條件但不是充分條件。
因為可能會有這種情況:
在兩兩相交的前提下,如果有一個小圓內含在一個大圓內的話,那麼這三個圓也是相交的。
否則,如果三個圓有公共部分,兩兩圓必然有1∼2 個交點。
如圖:
考慮這三個圓的相交區域,它必然是由若干個圓弧組成的。
所以這塊區域的關鍵點也一定是某兩個圓的交點,列舉兩兩圓的共三組交點,如果有一個交點滿足都在三個圓的圓內或圓上 ,那麼這三個圓就是相交的。
我的是二分了200次。
AC程式碼:
//#pragma comment(linker, "/STACK:102400000,102400000")
//#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <bitset>
#include <iomanip>
#include <list>
#include <complex>
#include <stack>
#include <utility>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef vector<int> vi;
const double eps = 1e-8;
const int INF = 1e9+7;
const ll inf =(1LL<<62) ;
const int MOD = 1e9 + 7;
const ll mod = (1LL<<32);
const int N =1e6+6;
const int M=100010;
const int maxn=1001;
#define mst(a) memset(a, 0, sizeof(a))
#define M_P(x,y) make_pair(x,y)
#define pi acos(-1.0)
#define in freopen("in.txt","r",stdin)
#define rep(i,j,k) for (int i = j; i <= k; i++)
#define per(i,j,k) for (int i = j; i >= k; i--)
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
const int lowbit(int x) { return x&-x; }
int read(){ int v = 0, f = 1;char c =getchar();
while( c < 48 || 57 < c ){if(c=='-') f = -1;c = getchar();}
while(48 <= c && c <= 57) v = v*10+c-48, c = getchar();
return v*f;}
#define point complex<double>
double t1, t2;
point cinema, shop, house;
void readpoint(point &p)
{
double x, y;
scanf("%lf %lf", &x, &y);
p = point(x, y);
}
bool inter(point a, double r_a, point b, double r_b, point c, double r_c) //以c為主圓求a b焦點判相交
{
if (abs(c - a) <= r_a && abs(c - b) <= r_b) return true;
b -= a;
c -= a; //以a為原點
point r = point(b.real() / abs(b), b.imag() / abs(b)); //將x軸正方向置為b
b /= r;
c /= r;
double d = (r_a * r_a - r_b * r_b + abs(b) * abs(b)) / (2 * abs(b));
double h = sqrt(max(r_a * r_a - d * d, 0.0));
if (abs(h * h + (d - abs(b)) * (d - abs(b))) - r_b * r_b > eps) return false;
if (abs(point(d, h) - c) <= r_c || abs(point(d, -h) - c) <= r_c) return true;
return false;
}
bool check(point a, double r_a, point b, double r_b, point c, double r_c) //判斷三圓是否相交
{
if (r_a <= eps || r_b <= eps || r_c <= eps) return false; //有空集
r_a = max(r_a, 0.0);
r_b = max(r_b, 0.0);
r_c = max(r_c, 0.0);
if (inter(a, r_a, c, r_c, b, r_b)) return true;
if (inter(b, r_b, a, r_a, c, r_c)) return true;
if (inter(c, r_c, b, r_b, a, r_a)) return true;
return false;
}
int main()
{
scanf("%lf %lf", &t1, &t2);
readpoint(cinema); //cinema
readpoint(house); //house
readpoint(shop); //shop
if (abs(shop - cinema) + abs(house - shop) <= abs(cinema - house) + t2)//Alan <= Bob + t2
{
printf("%lf\n", min( abs(cinema - house) + t2, abs(shop - cinema) + abs(house - shop) + t1));
}
else
{
double l, r, mid;
l = 0;
r = min( abs(cinema - house) + t2, abs(shop - cinema) + abs(house - shop) + t1);
for(int i=1;i<=200;i++)
{
mid = (r + l) / 2;
if (check(cinema, mid, shop, abs(shop - cinema) + t1 - mid, house, abs(house - cinema) + t2 - mid)){
l = mid;
}
else {
r = mid;
}
}
printf("%.4lf\n", l);
}
return 0;
}