2013国内予選 E. つながれた風船

E. つながれた風船

解法

  • 色々やり方はあるけど、ここでは円の問題に落として2分探索する方針
  • 高さhまで風船が上げられるかという問題を考える
    • 高さを決めれば、各ヒモの先端がどの範囲を動けるかは円で表せる
    • その円に共通部分が存在すれば、風船は高さhに上がる事ができる
  • 共通部分があれば、それは2つの円の交点か、円の中心のどれかを含む
    • ある円が他の全ての円に内包されるケースがあるので、交点だけだと不十分
    • 含む点の全ての候補について、その点が全ての円に内包されてるか調べれば良い
      • 1個でも内包しない円があれば、その点は共通部分上には無い
  • 高さhに上がれない -> 高さh'(>h)にも上がれない なので、あとは2分探索
#include <iostream>
#include <vector>
#include <complex>
#include <cstdio>
#include <cmath>

using namespace std;

const double EPS = 1e-10;

typedef complex<double> P;
typedef pair<P,double> circle;

double cross(P a, P b) { return imag(conj(a)*b); }

bool containPoint(const circle& c, const P& p){
    double dist = abs(c.first-p);
    return dist < c.second+EPS;
}

bool crossCircle(const circle& c1, const circle& c2){
    double r1 = c1.second;
    double r2 = c2.second;
    double d = abs(c1.first-c2.first);
    if(d<r1||d<r2)
        return d-abs(r1-r2)>EPS;
    return r1+r2-d>-EPS;
}

void crossPoint(vector<P> &res, const circle& c1, const circle& c2){
    double r1 = c1.second;
    double r2 = c2.second;
    double r3 = abs(c1.first-c2.first);
    double rc = (r3*r3+r1*r1-r2*r2)/(2*r3);
    double rs = sqrt(r1*r1-rc*rc);
    P dif = (c2.first-c1.first)/r3;
    res.push_back(c1.first+dif*P(rc, rs));
    res.push_back(c1.first+dif*P(rc,-rs));
}

bool valid(const vector<circle>& vc){
    vector<P> vp;
    for(int i=0;i<vc.size();i++){
        vp.push_back(vc[i].first);
        for(int j=i+1;j<vc.size();j++){
            if(crossCircle(vc[i], vc[j]))
                crossPoint(vp, vc[i], vc[j]);
        }
    }
    for(int i=0;i<vp.size();i++){
        bool ok = true;
        for(int j=0;j<vc.size();j++)
            if(!containPoint(vc[j], vp[i])) ok = false;
        if(ok) return true;
    }
    return false;
}

int main(){
    int n;
    int x[10], y[10], l[10]; 
    while(cin >> n &&  n){
        int minL = 100000;
        for(int i=0;i<n;i++){
            cin >> x[i] >> y[i] >> l[i];
            minL = min(minL, l[i]);
        }
        double low = 1.0, high = minL;
        for(int i=0;i<200;i++){
            double mid = 0.5*(low+high);
            vector<circle> vc;
            for(int j=0;j<n;j++){
                double r = sqrt(l[j]*l[j]-mid*mid);
                vc.push_back(make_pair(P(x[j], y[j]), r));
            }
            if(valid(vc)) low  = mid;
            else          high = mid;
        }
        printf("%.8lf\n", 0.5*(low+high));
    }
}