반응형

https://www.acmicpc.net/problem/3878

 

3878번: 점 분리

평면 위에 여러 개의 검정 점과 흰 점이 있다. 이때, 길이가 무한대인 직선을 그어 흰 점과 검은 점을 분리하려고 한다. 직선은 어떤 점과도 만나면 안 된다. 직선으로 인해서 나누어지는 두 그룹

www.acmicpc.net

 

흰 점과 검은 점이 분리가 되는지 확인하려면,

각 색 그룹의 점들을 모두 포함하는 두 개의 컨벡스헐을 만들어낸 뒤,

두 컨벡스헐이 서로 포함 관계에 있지 않고, 교차되는 부분이 없다면 분리시킬 수 있습니다.

서로 포함 관계에 있는지는 다음과 같이 알 수 있습니다.

설명 이미지

위와 같이 두 개의 컨벡스헐 A, B가 있고, A의 내부에 B를 포함하고 있습니다.

B에서 임의의 점 X를 선택하여 A의 모든 변과 ccw를 구하여 방향성을 확인해보겠습니다.

A의 변을 시계 방향으로 돌려가며 X와 ccw를 구한다면, 모든 변에 대하여 시계 방향으로 나오니 동일한 값이 나오게 됩니다.

만약 외부에 있는 임의의 점 Y를 선택하여 A의 모든 변과 ccw를 구한다면, 동일한 값이 나오지 않고 방향성이 달라지게 됩니다.

특정한 색의 그룹 내에 다른 색 점이 하나라도 포함된다면, 직선으로 점을 분리할 수 없게 됩니다.

위 과정에서 검사했던 점이 Y처럼 포함되어있지 않더라도, 분리시킬 수 있는지 완벽하게 확인하진 못합니다.

Y는 포함되지 않더라도 그 외의 다른 좌표는 A내에 포함될 수도 있고, n과 m이 다각형을 이루지 못할 정도로 그 좌표의 개수가 적어서 포함 관계를 확인하지 못할 수도 있습니다.

그래서 두 컨벡스헐이 교차되는지 확인하기 위하여 모든 변들에 대해서 교차 여부를 검사하면 됩니다.

설명 이미지

위와 같이 두 선분이 교차하는 그림 1번이 있을 때, 교차하는지 확인하려면 ccw(A,B,C)와 ccw(A,B,D)가 서로 다른 방향을 가지는지 확인하면 됩니다. 즉 ccw(A,B,C) * ccw(A,B,D) < 0이면 두 선분이 교차한다는 것을 알 수 있습니다.

하지만 그림 2번에서는, ccw(A,B,C) * ccw(A,B,D) < 0이더라도, 두 선분은 교차하지 않습니다.

이를 검사하기 위해 다른 선분에 대해서도 ccw를 구해주면 됩니다.

ccw(C, D, A) * ccw(C,D,B) > 0이기 때문에 두 선분은 교차하지 않습니다.

따라서 ccw(A,B,C) * ccw(A,B,D) < 0 && ccw(C, D, A) * ccw(C,D,B) < 0 이면, 두 선분은 교차한다고 볼 수 있습니다.

다음으로 그림 3번처럼 두 선분 A-B와 C-D가 일직선으로 놓여져있어서 ccw(A,B,C) * ccw(A,B,D) == 0 && ccw(C,D,A) * ccw(C,D,B) == 0인 상황이 있을 수 있습니다.

두 직선이 서로 떨어져있다면 점을 분리할 수 있지만, 서로 겹쳐져있다면 점을 분리할 수 없습니다.

이 경우에는 두 직선이 겹치는지 알기 위해 C <= B && A <= D이라면 교차한다고 보게 됩니다.

또, 4번과 같이 선분 위의 한 점에서 다른 선분의 끝 점이 겹치는 상황이 있을 수 있습니다. 저 경우에도 분리할 수 없으므로 교차한다고 판별이 됩니다.

그림같은 상황에서는, ccw(A,B,C) > 0, ccw(A,B,D) == 0, ccw(C,D,A) < 0, ccw(C,D,B) > 0이므로,

ccw(A,B,C) * ccw(A,B,D) == 0,

ccw(C,D,A) * ccw(C,D,B) < 0이 나오게 됩니다.

즉, 두 ccw곱이 모두 음수일 때 뿐만 아니라 하나가 0일 때도 교차로 판별이 됩니다.

정리하면,

ccw(A,B,C) * ccw(A,B,D) == 0 && ccw(C,D,A) * ccw(C,D,B) == 0 일 때,

(C <= B && A <= D)가 참이면 교차 // (A < B, C < D)

그렇지 않다면,

(ccw(A,B,C) * ccw(A,B,D) <= 0 && ccw(C, D, A) * ccw(C,D,B) <= 0)가 참이면 교차

입니다.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long ll;

int t, n, m;
ll x, y, bx, by;
vector<pair<ll, ll>> blackPos, whitePos, blackHull, whiteHull;

// 시계 음수, 반시계 양수, 일직선 0
int ccw(ll x1, ll y1, ll x2, ll y2, ll x3, ll y3) {
	ll cw = (x1 * y2 + x2 * y3 + x3 * y1) - (y1 * x2 + y2 * x3 + y3 * x1);
	if (cw > 0) return 1;
	if (cw < 0) return -1;
	return 0;
}

// x좌표 오름차순, y좌표 오름차순 정렬
// 가장 상단 왼쪽이 기준점
bool compAscXAscY(pair<ll, ll>& a, pair<ll, ll>& b) {
	if (a.first == b.first) return a.second < b.second;
	else return a.first < b.first;
}

// 기준점에서 시계 반대 방향으로 정렬
bool comp(pair<ll, ll>& a, pair<ll, ll>& b) {
	int res = ccw(bx, by, a.first, a.second, b.first, b.second);
	if (res == 0) { // 일직선인 경우 상단 좌측 순
		if (a.first == b.first) return a.second < b.second;
		else return a.first < b.first;
	}
	else {
		return res > 0;
	}
}

void createConvexHull(vector<pair<ll, ll>>& pos, vector<pair<ll, ll>>& hull) {
	sort(pos.begin(), pos.end(), compAscXAscY);
	bx = pos[0].first, by = pos[0].second; // 기준점 조정
	sort(pos.begin() + 1, pos.end(), comp);
	hull.push_back(pos[0]);
	if (pos.size() == 1) return;
	hull.push_back(pos[1]);

	for (int i = 2; i < pos.size(); i++) {
		while (hull.size() >= 2) {
			pair<ll, ll> a = hull.back();
			hull.pop_back();
			pair<ll, ll> b = hull.back();
			ll res = ccw(pos[i].first, pos[i].second, a.first, a.second, b.first, b.second);
			if (res < 0) {
				hull.push_back(a);
				break;
			}
		}
		hull.push_back(pos[i]);
	}
}

void inputPos(vector<pair<ll, ll>>& pos, int size) {
	for (int i = 0; i < size; i++) {
		scanf("%lld %lld", &x, &y);
		pos.push_back({ x, y });
	}
}

// 좌표가 outer 안에 포함되어있는지 확인
// 컨벡스헐의 모든 변과 해당 좌표가 같은 방향을 이루는지 검사
bool isInside(vector<pair<ll, ll>>& outer, pair<ll, ll>& p) {
	if (outer.size() <= 2) return false; // 정점이 2개 이하면 포함시킬 수 없음
	int cwBase = ccw(outer[0].first, outer[0].second, outer[1].first, outer[1].second, p.first, p.second);
	int sz = outer.size();
	for (int i = 1; i < sz; i++) {
		int cw = ccw(outer[i].first, outer[i].second, outer[(i + 1) % sz].first, outer[(i + 1) % sz].second, p.first, p.second);
		if (cw != cwBase) return false;
	}
	return true;
}

// a-b, c-d가 교차하는지 확인
// ccw(a,b,c) * ccw(a,b,d) == 0 && ccw(b,d,a) * ccw(b,d,c) == 0이면
// a--c--b--d 형태일 때 교차했다고 봄
// 그렇지 않으면,
// ccw(a,b,c) * ccw(a,b,d) <= 0 && ccw(b,d,a) * ccw(b,d,c) <= 0이면 교차
bool isCross(pair<ll, ll> a, pair<ll, ll> b, pair<ll, ll> c, pair<ll, ll> d) {
	int ab = ccw(a.first, a.second, b.first, b.second, c.first, c.second)
		* ccw(a.first, a.second, b.first, b.second, d.first, d.second);
	int cd = ccw(c.first, c.second, d.first, d.second, a.first, a.second)
		* ccw(c.first, c.second, d.first, d.second, b.first, b.second);
	if (ab == 0 && cd == 0) {
		// 겹치는 부분이 있는지 확인
		// first 비교 후, second 비교
		if (a > b) swap(a, b);
		if (c > d) swap(c, d);
		return c <= b && a <= d;
	}
	else {
		return ab <= 0 && cd <= 0;
	}
}

// 흰색, 검정색 두 개의 그룹으로 나눌 수 있는지 확인
bool isDivide() {

	// 두 개의 컨벡스헐이 서로 포함 관계에 있는지 검사
	// 그룹 내에 다른 색 점이 하나라도 포함된다면 직선으로 점을 분리할 수 없음 
	if (isInside(blackHull, whiteHull[0]) || isInside(whiteHull, blackHull[0])) return false;

	// 두 컨벡스헐의 모든 엣지가 교차하지 않는지 검사
	int bsz = blackHull.size();
	int wsz = whiteHull.size();
	for (int i = 0; i < bsz; i++) {
		for (int j = 0; j < wsz; j++) {
			if (isCross(blackHull[i], blackHull[(i + 1) % bsz],
				whiteHull[j], whiteHull[(j + 1) % wsz])) {
				return false;
			}
		}
	}
	return true;
}


int main() {
	scanf("%d", &t);
	while (t--) {
		blackPos.clear();
		whitePos.clear();
		blackHull.clear();
		whiteHull.clear();

		scanf("%d %d", &n, &m);

		inputPos(blackPos, n);
		inputPos(whitePos, m);

		createConvexHull(blackPos, blackHull);
		createConvexHull(whitePos, whiteHull);

		if (isDivide()) {
			printf("YES\n");
		}
		else {
			printf("NO\n");
		}
	}
}

반응형

'Algorithm' 카테고리의 다른 글

백준 17387 : 선분 교차 2  (0) 2021.11.18
백준 2254 : 감옥 건설  (0) 2021.11.18
백준 3679 : 단순 다각형  (0) 2021.11.18
백준 7420 : 맹독 방벽  (0) 2021.11.18
백준 9240 : 로버트 후드  (0) 2021.11.18

+ Recent posts