vector<point> ch(st.size()); for (auto& p : ch) p = v[st.top()], st.pop(); return ch; }
이후 넓이를 구하는 부분이 필요하기 때문에 외적하는 부분을 area()로 따로 분리한 것 이외는 차이가 없다.
윗껍질, 아랫껍질 분리하기
컨벅스 헐을 구했으면 넓이를 계산하기 위해 윗 껍질과 아래껍질로 분리했다.
컨벡스 헐을 구하는 알고리즘 중에 모노톤 체인 기법을 이용하면 편리하게 분리할 수 있겠지만 그라함 스캔이 익숙해서 컨벡스 헐을 구한 뒤 이를 분리했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
voidsep(vector<point>& ch, vector<point>& up, vector<point>& down){ int l = (int)ch.size(); up.push_back(ch[l - 1]); down.push_back(ch[l - 1]);
int i = 0; while (ch[i][0] <= ch[i + 1][0]) { up.push_back(ch[i++]); } up.push_back(ch[i]); int j = l - 2; while (i <= j) { down.push_back(ch[j--]); } }
내가 사용하는 컨벡스 헐은 정점들이 시계방향으로 담겨있기 때문에 방향에 유의해서 up과 down 벡터에 $x$좌표가 증가하는 방향으로 좌표들이 저장될 수 있게 했다.
[예제 1]을 sep() 함수까지 실행하면 아래와 같이 윗껍질과 아래껍질이 분리되는 것을 볼 수 있다.
solve 함수 만들기 (1) - 컨벡스 헐과 직선의 교점 구하기
이분 탐색을 돌 때 조건을 어떻게 만족하는지에 따라 lo와 hi를 변경하는데 이를 판별하기위한 solve() 함수가 필요하다.
solve() 함수에서는 $x=mid$에서 왼쪽 구역과 오른쪽 구역을 나누고 이 두 구역의 넓이를 비교한다.
왼쪽 구역과 오른쪽 구역을 나누기 위해서는 컨벡스 헐과 $x=mid$의 교점이 필요하다.
교점이 한 껍질의 두 점 $a(x_1, y_1), b(x_2, y_2)$을 잇는 선분에 생길 때 우리는 교점의 $x$좌표인 $mid$를 알고 있기 때문에 up과 down에서 $x$좌표에 대해 $mid$로 lower_bound를 사용한다면 $b$의 좌표를 구할 수 있다.
$a$점은 $b$점의 바로 이전 점이기 때문에 추가적으로 구할 필요가 없다.
컨벡스 헐은 각 점을 직선으로 연결하기 때문에 선형 보간을 이용해 선분과 직선의 교점$(x, y)$을 찾을 수 있다.
$$ x = mid \quad y = y_1 + t(y_2 - y_1) \quad t=\frac {(mid - x_1)}{(x_2 - x_1)} $$
1 2 3 4 5 6 7 8 9 10 11 12
point intersection(point& a, point& b, double c){ double t = (c - a[0]) / (b[0] - a[0]); return {c, a[1] + t * (b[1] - a[1])}; }
intsolve(vector<point>& up, vector<point>& down, double mid){ point m = {mid, 0}; int ui = lower_bound(up.begin(), up.end(), m) - up.begin(); int di = lower_bound(down.begin(), down.end(), m) - down.begin();
point u = intersection(up[ui - 1], up[ui], mid); point d = intersection(down[di - 1], down[di], mid);
lower_bound를 통해 윗껍질과 아랫껍질에서 교차하는 선분을 구하고, intersection() 함수에서 선분과 만나는 교점을 구해주었다.
solve 함수 만들기 (2) - 두 영역의 넓이 구하기
영역을 구할 때 2166 - 다각형의 면적 문제처럼 다각형의 이루는 순서대로 배열에 담겨 있으면 쉽게 넓이를 구할 수 있다.
따라서 아래와 같이 시계 반대 방향 순서로 점들을 점들을 담아주었다.
1 2 3 4 5 6 7 8 9 10 11 12 13
vector<point> p1; p1.reserve(up.size() + down.size()); for (int i = 0; i < di; i++) p1.push_back(down[i]); p1.push_back(d); p1.push_back(u); for (int i = ui - 1; i > 0; i--) p1.push_back(up[i]);
vector<point> p2; p2.reserve(up.size() + down.size()); p2.push_back(d); for (int i = di; i < down.size(); i++) p2.push_back(down[i]); for (int i = up.size() - 2; i >= ui; i--) p2.push_back(up[i]); p2.push_back(u);
만약 담는 과정에서 교점이 컨벡스 헐을 구성하는 점을 지나간다면 같은 점이 두 번 배열에 담길 수도 있지만, CCW를 계산할 때 0이 나와 상관이 없다.
1 2 3 4 5 6 7 8 9 10 11 12 13
doublearea(vector<point>& a){ double sum = 0; for (int i = 2; i < (int)a.size(); i++) { sum += abs(area(a[0], a[i - 1], a[i])); } return sum; }
intsolve(vector<point>& up, vector<point>& down, double mid){ // 생략 double left = area(p1), right = area(p2); return left < right; }
area() 함수를 통해 p1배열과 p2 배열의 넓이를 각각 구해주고 왼쪽 부분과 오른쪽 부분 넓이를 비교한 결과를 반환했다.
이분 탐색 만들기
lo는 $x_{min}$, hi는 $x_{max}$로 설정하고 (lo + 1e-5 < hi)를 만족할 때까지 이분 탐색을 반복했다.
solve() 함수가 true면 왼쪽 구역의 넓이가 더 작으므로 lo = mid로 설정하고,
false면 오른쪽 구역의 넓이가 더 작으므로 hi = mid로 설정했다.
반복이 끝났으면 lo, hi의 차이가 $10^{-5}$보다 작아 lo, hi중 둘 중 아무거나 출력해도 문제의 출력조건 절대/상대 오차가 $10^{-3}$ 이하를 만족해서, lo를 출력했다.
1 2 3 4 5 6 7 8 9
double lo = up[0][0], hi = up[up.size() - 1][0]; while (lo+1e-5<hi) { double mid = (lo + hi) / 2; if (solve(up, down, mid)) lo = mid; else hi = mid; } cout << fixed; cout.precision(4); cout << lo;
vector<point> ch(st.size()); for (auto& p : ch) p = v[st.top()], st.pop(); return ch; }
voidsep(vector<point>& ch, vector<point>& up, vector<point>& down){ int l = (int)ch.size(); up.push_back(ch[l - 1]); down.push_back(ch[l - 1]);
int i = 0; while (ch[i][0] <= ch[i + 1][0]) { up.push_back(ch[i++]); } up.push_back(ch[i]); int j = l - 2; while (i <= j) { down.push_back(ch[j--]); } }
point intersection(point& a, point& b, double c){ double t = (c - a[0]) / (b[0] - a[0]); return {c, a[1] + t * (b[1] - a[1])}; }
doublearea(vector<point>& a){ double sum = 0; for (int i = 2; i < (int)a.size(); i++) { sum += abs(area(a[0], a[i - 1], a[i])); } return sum; }
intsolve(vector<point>& up, vector<point>& down, double mid){ point m = {mid, 0}; int ui = lower_bound(up.begin(), up.end(), m) - up.begin(); int di = lower_bound(down.begin(), down.end(), m) - down.begin();
point u = intersection(up[ui - 1], up[ui], mid); point d = intersection(down[di - 1], down[di], mid);
vector<point> p1; p1.reserve(up.size() + down.size()); for (int i = 0; i < di; i++) p1.push_back(down[i]); p1.push_back(d); p1.push_back(u); for (int i = ui - 1; i > 0; i--) p1.push_back(up[i]);
vector<point> p2; p2.reserve(up.size() + down.size()); p2.push_back(d); for (int i = di; i < down.size(); i++) p2.push_back(down[i]); for (int i = up.size() - 2; i >= ui; i--) p2.push_back(up[i]); p2.push_back(u);