알고리즘/알고리즘 노트

[기하]

산을좋아한라쯔 2016. 7. 25. 07:53
반응형

필수로 알아야할 내용

1. 사선 정리 : 다각형 면적, 각도 방향성(CCW,CW,직선) 판별 --> 외적과 동일

2. 내적(직각여부, 투영), 외적(면적, 각도방향성) 

3. 점과 직선 사잇거리

4. 라인 교차

5. 내/외부 판별

6. convex hull


---------------

1. 사선 정리

세 점 P(x1,y1), A(x2,y2), B(x3,y3)가 있을 때, 삼각형 APB의 면적은? 각 APB의 방향은?


S=1/2 * (x1y2+x2y3+x3y1-x2y1-x3y2-x1y3)


S의 부호를 가지고 각도의 방향이 CCW(반시계방향)이지 CW(시계방향)인지를 판가름하는 것은, 두 벡터의 외적을 가지고 각도의 방향을 판가름 하는 것으로부터 유도 가능. 

즉, 방향 = ((A-P) x (B-P)) > 0 ? CCW : CW 


사선공식은 세 점이 아닌 n개의 점으로 이루어진 다각형의 합에도 동일하게 이용 가능


2. 내적, 외적

A(x1,y1), B(x2,y2)에 대해서,


내적: A dot B = x1x2 + y1y2 = |A| |B| cos(s)  //s는 A와 B 사잇각

외적: A x B = x1y2 - x2y1


*내적의 활용

- 두 벡터의 직각 여부: if(A dot B == 0) then OA 와 OB는 직각. 

  왜냐하면 s = acos[(A dot B) / |A| |B|] 이기에, A dot B가 0이면 s는 90도 혹은 270도(시계방향으로 직각)

- 다른 벡터로의 투영(projection)


 

B의 단위벡터를 r이라 했을 때, 

   r = (x2/|B|, y2/|B|)


A와 r의 내적은,

  A dot r = |A| cos(s)  (왜냐면 |r|=1 )


A의 B로의 투영한 길이는 |A| cos(s)이므로, 투영한 벡터는 단위벡터 r을 이 길이만큼 늘린 것.

따라서, 

  투영벡터 = r * (A dot r) = r * (|A| cos(s)


* 외적의 활용

- 면적: 두 벡터의 외적은, 두 벡터를 가지고 만들어지는 평행사변형의 면적. 따라서, 1/2을 하면 삼각형의 면적(사선공식과 동일)

- 방향: 

   A x B > 0 ==> A를 기준으로 해서, B가 왼쪽에 있음(CCW)

         <  0 ==> CW

         =  0 ==> 직선


3. 점과 직선 사잇거리

점P에서 직선AB사잇 거리



- P를 AB에 투영한 벡터 Q를 구한다. 

  벡터 (B-A)의 단위벡터를 r이라 하면,

  Q = r * (P dot r) 

- 거리는 |P-Q|



4. 라인 교차 여부

- 두 선분(네 점)이 주어졌을 때, 교차여부 판단. : a, b, c, d

   --> 선분 ab를 기준으로 c와 d가, 선분 cd를 기준으로 a, b가 각기 다른 방향에 있는 지 확인


struct P {

         int x, y;

 

         P() {}

         P(int _x, int _y) {        x = _x; y = _y;   }

        

         P operator - (P& rhs) const { return P(x-rhs.x, y-rhs.y);}

         bool operator < (P& rhs) { return (x != rhs.x) ? (x < rhs.x) : (y < rhs.y);     }

};

 

int cross(P a, P b) {      return a.x * b.y - a.y*b.x; }

int ccw(P a, P b, P c) {return cross(b - a, c - a);}

 

bool isInter(P a, P b, P c, P d) {

         int ab = ccw(a, b, c) * ccw(a, b, d);

         int cd = ccw(c, d, a) * ccw(c, d, b);

         if (ab == 0 && cd == 0) {

                  if (b < a) swap(a, b);

                  if (d < c) swap(c, d);

                  // [a..b] 경우만 true. , !( d<- a~b -> c )

                  return !(d<a || b<c);

         }

         return ab <= 0 && cd <= 0;

}



5. 내/외부 판별

- 여러 점들로 이루어진 다각형(볼록, 오목 상관없음)이 있을 때, 어떤 한 점이 이 다각형의 내부에 있는 지 외부에 있는 지를 판별.

- 기본 원리는, 그 점에서 오른쪽 방향으로 선을 그었을 때 다각형의 선분과 홀수번 만나는 지, 짝수번 만나는 지를 파악하는 것.

  홀수번: 내부, 짝수번: 외부

- 이 때 주의할 것은, 아래 그림과 같은 케이스들에 대해서, 그림에서 카운팅된 것처럼 제대로 카운팅되야 하는 것
  이를 위해서는 라인에 대해서, 밑에 점과 위에 점 중에서, 어느하나만 카운트 되야하고, 만약 평행하다면 하나도 카운트 되지 말아야 함.
  이렇게 하도록 하는 것이 아래 코드.
     if( (p[i].y > q.y) != (p[j].y > q.y))


  이것을 p[i].y=y1, p[j].y=y2, q.y=y라 했을 때, if ((y1>y) != (y2>y)) 가 되고, 이를 어떤 경우에 참이 되는 지 진리표로 보면,

  (y1>y) = T  && (y2>y)=F 의 경우와, (y1>y)=F && (y2>y)=T의 경우만 참이 된다.

  첫번 째 경우는, y가 (~,y1) 이고 (y2,~)일 때이고 (y1 밑 혹은 y2 위) 이는 가능하지 않은 경우이고

   (y1보다 작으면서 y2보다 클 수 없음 혹은 그반대도 불가)

  두번 째 경우는, y가 [y1,y2) 사이에 있을 때로, 가능한 경우이다. 즉, if ((y1>y) != (y2>y))은 y가 [y1,y2)사이에 있을 때라는 얘기.

- 점 q의 y좌표가, 다각형을 이루는 선분상의 y값 사이에 있다면,  

  이 때 q에서 이 선분으로 x축에 평행한 선을 그리면 만나는 점 atX를 구할 수 있다.

  두 점 (x1,y1) (x2,y2)로 이루어진 선분에서, y값이 y0라면, 그 때의 x0값은?

  y = ax +b 에서, a = (y2-y1)/(x2-x1), b=y1-ax1

  x0 = 1/a(y0-b)  --> 여기에 a, b값을 넣어 정리하면,

  x0 = (x2-x1)(y0-y1)/(y2-y1) + x1


소스

bool isInside(P q, vector<P>& p) {

         int cnt = 0;

         int x, x1, x2, y, y1, y2;

 

         x = q.x; y = q.y;

         for (int i = 0; i < p.size(); ++i) {

                  int j = (i + 1) % p.size();

                  x1 = p[i].x; y1 = p[i].y;

                  x2 = p[j].x; y2 = p[j].y;

                 

                  //핵심. [low..high) . 낮은쪽 점과 위쪽 라인을 지날 때만. 평행의 경우는 해당 안됨

                  if ((y1 > y) != (y2 > y)) {

                           // 점을 이루는 직선상에서, y점에 대한 x

                           double atX = (x2 - x1) * (y - y1) / (y2 - y1) + x1;

                           if (x < atX) ++cnt;

                  }

         }

         return (cnt % 2) > 0; //홀수이면 inside

}



6. convex hull

- 여러점들이 주어졌을 때, 가장 외곽에 있는 점들을 연결하여 볼록 다각형 만들기.
- Graham's 알고리즘으로 구하면 다음과 같다.

1)점들 중에서 y축으로 가장 작은 값을 찾는다. 여러 개 존재할 경우 x값이 가장 작은 것으로 선택
mini = a[1];
for (int i = 2; i <= n; i++) {
if (mini.y > a[i].y || mini.y == a[i].y && mini.x > a[i].x) mini = a[i];
}

2)mini를 기준으로 각 점을 이었을 때 생기는 각 기준으로 소팅. CCW방향 기준. 같은 값이면 mini로부터 거리가 먼 것이 큰 것
   (거리가 먼 것이 큰 것이기에, 소팅을 하면 제일 마지막쪽에 위치하게 된다는 것. 이렇게 소팅된것에 대해 직선을 그리는것을 상상해보라.)
bool comp(Point a, Point b) {
ll cr = ccw(mini,a,b);
if (cr > 0 || (cr == 0 && dist(mini, a) < dist(mini, b))) return true;
return false;
}

sort(a + 1, a + n + 1, comp); //mini가 포함되어 있지만 상관없음. 어차피 가장 작은 값임.

3)a[n]과 a[0]를 스택에 담고, a[1]~a[n]까지 진행하면서, 

  스택의 맨 위 두개 점으로 생기는 라인을 기준으로 새로 검사되는 점들의 CCW여부를 봐서,

    - CCW가 아니면, 스택의 맨 위 점을 pop하면서, 다시 스택의 두 점을 기준으로한 라인과 그 점의 CCW 확인

    - CCW이면 그 점을 스택에 넣음

for (int i = 2; i <= n; ++i) { //n-1이 아닌 n까지라는 것에 유의

while (ccw(st[st_top - 1], st[st_top],a[i]) <= 0) st_top--; //주의!! 

st[++st_top] = a[i];

}


4)스택에 남아있는 점들의 집합이 convex hull을 구성하는 점 집합이 됨


소스

#include <stdio.h>

#include <algorithm>

#include <math.h>

 

#define N 100003

using namespace std;

 

typedef long long ll;

 

struct Point {

         ll x, y;

         Point() {}

         Point(ll _x, ll _y) { x = _x;      y = _y; }

 

         Point operator - (const Point& rhs) const { return Point(x - rhs.x, y - rhs.y); }

         ll cross(Point q) { return (x*q.y - y*q.x); }

}mini, a[N], st[N];

 

int n;

 

ll ccw(Point p1, Point p2, Point p3) {

         return (p2 - p1).cross(p3 - p1);

}

 

double dist(Point a, Point b) {

         //여기서는, 상대적인 길이 대소만을 비교하는 것이기에 sqrt를 취하지 않음

         return (b.x - a.x) * (b.x - a.x) + (b.y - a.y)*(b.y - a.y);

}

 

bool comp(Point a, Point b) {

         ll cr = ccw(mini, a, b);

         if (cr > 0 || (cr == 0 && dist(mini, a) < dist(mini, b))) return true;

         return false;

}

 

int main() {

         //freopen("convex_hull.txt", "r", stdin);

 

         scanf("%d", &n);

         for (int i = 1; i <= n; ++i) {

                  scanf("%lld %lld", &a[i].x, &a[i].y);

         }

 

         //graham

         //1. find most below y. if same y, choose min x -> O(n) 

         mini = a[1];

         for (int i = 2; i <= n; i++) {

                  if (mini.y > a[i].y || mini.y == a[i].y && mini.x > a[i].x) mini = a[i];

         }

 

         //2. sort        

         sort(a + 1, a + n + 1, comp);

 

         //3. a[n]->a[0]에서부터 시작해서

         int st_top = 0;

         st[0] = a[n]; //주의!!!

         st[++st_top] = a[1];

 

         for (int i = 2; i <= n; ++i) { //n-1 아닌 n까지라는 것에 유의

                  while (ccw(st[st_top - 1], st[st_top], a[i]) <= 0) st_top--; //주의!!

                  st[++st_top] = a[i];

         }

 

         printf("%d", st_top);

 

         return 0;

}

-끝-

반응형

'알고리즘 > 알고리즘 노트' 카테고리의 다른 글

[다이나믹]타일채우기 문제들  (0) 2016.07.26
[다이나믹]  (0) 2016.07.26
[그래프]  (0) 2016.07.24
[이분탐색]  (0) 2016.07.23
[완전탐색]DFS, BFS  (0) 2016.07.23