CGL_2_D 距離

Calendar Clock iconCalendar Clock icon

AOJ

目次

# 問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_2_D

2つの線分間の距離をもとめる問題.

# 解説

線分というのは2点間を結ぶ線.
ありえるケースは、

  1. 2線分が交差する → 距離は0
  2. 2線分が交差しない → 2線分の頂点4つについての以下の最小値
  3. 頂点vと、vが属さない線分sの距離

頂点vと線分sの距離は以下のようにしてもとめる.

点と線分をともに移動しても答えの距離は変化しない.
考えやすいように線分を水平に配置し点をその上部に来るように頭の中で考える.
すると、点と線分の関係は以下の3通り.
簡単のため、線分の左側の頂点を始点、右側を終点と呼ぶ.

  1. 点vは始点よりも左にある → 距離は点vと始点との距離
  2. 点vは終点よりも右にある → 距離は点vと終点との距離
  3. 点vは線分の上部に収まっている → 距離は線分を底辺とする点vと線分sがなす平行四辺形の高さ

点vが線分外側にあるかの判定は内積を取ればわかる.
負であれば鈍角なので外側.
平行四辺形の高さは底辺の長さが分かっているので面積を求めるだけでOK.
面積はベクトルのなす外積の絶対値.

# 解答

// C++ 14
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <unordered_map>
#include <math.h>

#define ll long long
#define Int int
#define loop(x, start, end) for(Int x = start; x < end; x++)
#define loopdown(x, start, end) for(int x = start; x > end; x--)
#define rep(n) for(int x = 0; x < n; x++)
#define span(a,x,y) a.begin()+x,a.begin()+y
#define span_all(a) a.begin(),a.end()
#define len(x) (x.size())
#define last(x) (*(x.end()-1))

using namespace std;

#define EPS 0.0000000001
#define fequals(a,b) (fabs((a) - (b)) < EPS)

class Vector2 {
public:
  double x, y;
  
  Vector2(double x = 0, double y = 0): x(x), y(y) {}
  
  Vector2 operator + (const Vector2 v) const { return Vector2(x + v.x, y + v.y); }
  Vector2 operator - (const Vector2 v) const { return Vector2(x - v.x, y - v.y); }
  Vector2 operator * (const double k) const { return Vector2(x * k, y * k); }
  Vector2 operator / (const double k) const { return Vector2(x / k, y / k); }
  
  double length() { return sqrt(norm()); }
  double norm() { return x * x + y * y; }
  double dot (Vector2 const v) { return x * v.x + y * v.y; }
  double cross (Vector2 const v) { return x * v.y - y * v.x; }
  
  bool parallel(Vector2 &other) {
    return fequals(0, cross(other));
  }
  
  bool orthogonal(Vector2 &other) {
    return fequals(0, dot(other));
  }
  
  bool operator < (const Vector2 &v) {
    return x != v.x ? x < v.x : y < v.y;
  }
  
  bool operator == (const Vector2 &v) {
    return fabs(x - v.x) < EPS && fabs(y - v.y) < EPS;
  }
};

ostream & operator << (ostream & out, Vector2 const & v) { 
  out<< "Vector2(" << v.x << ", " << v.y << ')';
  return out;
}

istream & operator >> (istream & in, Vector2 & v) { 
  double x, y;
  in >> x;
  in >> y;
  v.x = x;
  v.y = y;
  return in;
}
// 点と線分の距離
double p2Seg(Vector2 start_, Vector2 end_, Vector2 p) {
  // 1. 線分の始点よりも左だったら始点との距離
  if ((p - start_).dot(end_ - start_) < 0.0) return (p - start_).length();
  // 2. 線分の終点よりも右だったら終点との距離
  if ((p - end_).dot(start_ - end_) < 0.0) return (p - end_).length();
  // 3. 線分の上にある場合線分へ垂らした垂線の長さ(外積は平行四辺形の面積なので、底辺で割ると高さ=垂線の長さがもとまる)
  return abs((end_ - start_).cross(p - start_)) / (end_ - start_).length();
}

double distance(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) {
  // 1. 2線分が交差するなら距離0
  double t3 = (p1.x - p2.x) * (p3.y - p1.y) + (p1.y - p2.y) * (p1.x - p3.x);
  double t4 = (p1.x - p2.x) * (p4.y - p1.y) + (p1.y - p2.y) * (p1.x - p4.x);
  double t1 = (p3.x - p4.x) * (p1.y - p3.y) + (p3.y - p4.y) * (p3.x - p1.x);
  double t2 = (p3.x - p4.x) * (p2.y - p3.y) + (p3.y - p4.y) * (p3.x - p2.x);
  if (t3 * t4 < 0 && t1 * t2 < 0) return 0.0;

  // 2. 交差しなければ4つの点と相手の線分の距離が最も小さいもの
  double min_ = -1;
  min_ = min<double>(p2Seg(p1, p2, p3), p2Seg(p1, p2, p4));
  min_ = min<double>(min_, p2Seg(p3, p4, p1));
  min_ = min<double>(min_, p2Seg(p3, p4, p2));
  return min_;
}

#define MAX_N 1001
Int N;
Vector2 p1, p2, p3, p4;

void input() {
  cin >> N;
  while (cin >> p1 >> p2 >> p3 >> p4) {
    cout << distance(p1, p2, p3, p4) << endl;
  }
}

int main() {
  cout.precision(15);
  input();
}

リモートフリーランス。ウェブサービス、スマホアプリエンジニア。
東アジアを拠点に世界を移動しながら活動してます!

お仕事のご依頼・お問い合わせはこちら

コメント