본문 바로가기

백준

20149 선분 교차 3

으으으 구현 열심히 했는데 실수가 있었는지 틀렸다.

누더기 된 코드 확인하는 것보다 멀끔한 북님 코드 분석하는게 더 좋을 것 같아서 개쓰래기 코드를 들고 오지는 않겠다. 

 

아래는 북님 코드 전문이고 이제 분석도 해보자

#include <stdio.h>
#include <vector>
#include <queue>
#include <algorithm>
#include <iostream>
#include <string>
#include <bitset>
#include <map>
#include <set>
#include <tuple>
#include <string.h>
#include <math.h>
#include <random>
#include <functional>
#include <assert.h>
#include <math.h>
#define all(x) (x).begin(), (x).end()
#define xx first
#define yy second

using namespace std;

template<typename T, typename Pr = less<T>>
using pq = priority_queue<T, vector<T>, Pr>;
using i64 = long long int;
using ii = pair<int, int>;
using ii64 = pair<i64, i64>;

i64 ccw(i64 x1, i64 y1, i64 x2, i64 y2, i64 x3, i64 y3)
{
	i64 cross = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);

	if (cross > 0)
		return 1;

	if (cross < 0)
		return -1;

	return 0;
}

bool intersect(i64 x1, i64 y1, i64 x2, i64 y2, i64 x3, i64 y3, i64 x4, i64 y4)
{
	int l1l2 = ccw(x1, y1, x2, y2, x3, y3) * ccw(x1, y1, x2, y2, x4, y4);
	int l2l1 = ccw(x3, y3, x4, y4, x1, y1) * ccw(x3, y3, x4, y4, x2, y2);

	if (l1l2 == 0 && l2l1 == 0)
	{
		if (ii64(x2, y2) < ii64(x1, y1))
		{
			swap(x1, x2);
			swap(y1, y2);
		}

		if (ii64(x4, y4) < ii64(x3, y3))
		{
			swap(x3, x4);
			swap(y3, y4);
		}

		return ii64(x3, y3) <= ii64(x2, y2) && ii64(x1, y1) <= ii64(x4, y4);
	}

	return l1l2 <= 0 && l2l1 <= 0;
}

i64 gcd(i64 a, i64 b)
{
	if (b == 0)
		return a;

	return gcd(b, a % b);
}

int main()
{
	i64 x1, y1, x2, y2, x3, y3, x4, y4;
	scanf("%lld %lld %lld %lld %lld %lld %lld %lld", &x1, &y1, &x2, &y2, &x3, &y3, &x4, &y4);

	if (!intersect(x1, y1, x2, y2, x3, y3, x4, y4))
	{
		printf("0\n");
		return 0;
	}

	printf("1\n");

	i64 dx1 = x1 - x2, dy1 = y1 - y2;
	i64 dx2 = x3 - x4, dy2 = y3 - y4;

	i64 g1 = gcd(dx1, dy1);
	dx1 /= g1;
	dy1 /= g1;

	
	i64 g2 = gcd(dx2, dy2);
	dx2 /= g2;
	dy2 /= g2;

	if (dx1 == dx2 && dy1 == dy2)
	{
		i64 x = min(max(x1, x2), max(x3, x4)) - max(min(x1, x2), min(x3, x4));
		i64 y = min(max(y1, y2), max(y3, y4)) - max(min(y1, y2), min(y3, y4));

		if (x > 0 || y > 0)
			return 0;

		if ((x1 == x3 && y1 == y3) || (x1 == x4 && y1 == x4))
			printf("%lld %lld\n", x1, y1);
		else
			printf("%lld %lld\n", x2, y2);

		return 0;
	}

	i64 pxu = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
	i64 pxd = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
	i64 pyu = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
	i64 pyd = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

	double px = (double)pxu / pxd;
	double py = (double)pyu / pyd;

	printf("%.12lf %.12lf", px, py);

	return 0;
}

 

선분 교차를 확인하는데 CCW 알고리즘이 쓰였다. CCW는 두 점의 방향성을 나타내는 알고리즘인데 이걸 사용해서 두 점이 교차하는지 알 수 있다.

 

구분할 수 있는 경우는 

- 반시계 방향인 경우

- 시계 방향인 경우

- 세 점이 평행한 경우 

 

공식은 (x2 -x1) * (y3 - y1) - (y2 - y1)(x3 - x1) 으로 판별 가능하다. 결과가 0보다 작으면 시계 방향, 결과가 0보다 크면 반시계 방향, 0이면 평행하다. 

 

CCW(A,B,C)*CCW(A,B,D)<=0 이면서 CCW(C,D,A)*CCW(C,D,B)<=0 이면 두 점이 교차하는지 확인할 수 있다. 

참고

https://jason9319.tistory.com/358

 

그리고 두 직선이 일직선이라면 겹칠 수도 있고 안 겹칠 수도 있어서 이 때는 범위를 확인해주면 된다.

 

이걸 알아야 깔끔히 풀 수 있구나!

 

---

 

아래는 조각조각 확인해보기

 

i64 ccw(i64 x1, i64 y1, i64 x2, i64 y2, i64 x3, i64 y3)
{
	i64 cross = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1);

	if (cross > 0)
		return 1;

	if (cross < 0)
		return -1;

	return 0;
}

 

CCW 알고리즘이다 (끄덕)

 

bool intersect(i64 x1, i64 y1, i64 x2, i64 y2, i64 x3, i64 y3, i64 x4, i64 y4)
{
	int l1l2 = ccw(x1, y1, x2, y2, x3, y3) * ccw(x1, y1, x2, y2, x4, y4);
	int l2l1 = ccw(x3, y3, x4, y4, x1, y1) * ccw(x3, y3, x4, y4, x2, y2);

	if (l1l2 == 0 && l2l1 == 0)
	{
		if (ii64(x2, y2) < ii64(x1, y1))
		{
			swap(x1, x2);
			swap(y1, y2);
		}

		if (ii64(x4, y4) < ii64(x3, y3))
		{
			swap(x3, x4);
			swap(y3, y4);
		}

		return ii64(x3, y3) <= ii64(x2, y2) && ii64(x1, y1) <= ii64(x4, y4);
	}

	return l1l2 <= 0 && l2l1 <= 0;
}

 

이건 겹치는지 확인하는 함수이다.

선분 두 개를 비교해서 CCW 결과를 파악한다. 

 

l1l2 <= 0 && l2l1 <= 0; 이게 겹치는 건지 파악하는 부분이다. 둘다 0인 경우는 평행한 경우인데 이 때는 좌표를 비교해서 겹치는지 확인한다. 

 

	printf("1\n");

 

이제 점을 열심히 구해보자. 이까지 온 거면 겹치긴 한다는 거니깐 1을 찍어 준다.

 

https://jason9319.tistory.com/358

아 갑자기 이런 경우 판단이 가능하나?? 궁금해졌다. AB를 기준으로 C랑 D의 방향을 구해보면 방향이 같다!!! 여기서 판단이 가능하구나. 개념 이해가 부족했다. 

 

그럼 다시 코드를 보자

 

	i64 dx1 = x1 - x2, dy1 = y1 - y2;
	i64 dx2 = x3 - x4, dy2 = y3 - y4;

	i64 g1 = gcd(dx1, dy1);
	dx1 /= g1;
	dy1 /= g1;

	
	i64 g2 = gcd(dx2, dy2);
	dx2 /= g2;
	dy2 /= g2;

 

기울기를 구하자

(y2 - y1) / (x2 - x1) 이니 dx, dy를 미리 구하자. 

 

if (dx1 == dx2 && dy1 == dy2)
	{
		i64 x = min(max(x1, x2), max(x3, x4)) - max(min(x1, x2), min(x3, x4));
		i64 y = min(max(y1, y2), max(y3, y4)) - max(min(y1, y2), min(y3, y4));

		if (x > 0 || y > 0)
			return 0;

		if ((x1 == x3 && y1 == y3) || (x1 == x4 && y1 == x4))
			printf("%lld %lld\n", x1, y1);
		else
			printf("%lld %lld\n", x2, y2);

		return 0;
	}

 

이제 기울기가 같은 경우를 확인한다. 

 

		i64 x = min(max(x1, x2), max(x3, x4)) - max(min(x1, x2), min(x3, x4));
		i64 y = min(max(y1, y2), max(y3, y4)) - max(min(y1, y2), min(y3, y4));

		if (x > 0 || y > 0)
			return 0;

이건 뭐지...? 일단 양수인 경우 바로 return 하니 완전 일치하는 경우를 판단하는 것 같다. 

 

x1, y1 = 1, 1 / x2, y2 = 5, 5 / x3, y3 = 2, 2 / x4, y4 = 6, 6 인 경우

 

min(5, 6) // 뒤에 좌표 중 작은 거

max(1, 2) //앞에 좌표 중 큰 거

아 이해갔다. 겹치는 부분 있는지 확인하는 거구나! 패스패스

 

뒤에 있는 것들은 일직선인데 한 점이 겹치는 경우 체크한 거고

 

	i64 pxu = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
	i64 pxd = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
	i64 pyu = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
	i64 pyd = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

	double px = (double)pxu / pxd;
	double py = (double)pyu / pyd;

	printf("%.12lf %.12lf", px, py);

 

이제 마지막으로 겹치는 점을 구합니다

끝! 

'백준' 카테고리의 다른 글

2873 롤러코스터  (0) 2021.07.20
16923 다음 다양한 단어  (0) 2021.07.18
2090 조화평균  (0) 2021.07.17
12850 본대 산책2  (0) 2021.07.17
16938 캠프 준비  (0) 2021.07.17