문제
1. 첫 번째 풀이
사고 과정
- 간단하게 조규현은 A, 백승환은 B, 마린은 마린으로 칭해보자.
- A로부터 반지름이 r1인 원을, B로부터 반지름이 r2인 원을 그릴 수 있다.
- 두 원은 두 점에서 만나거나, 한 점에서 만나거나, 만나지 않거나, 완전히 겹칠 수 있다.
- 완전히 겹치는 경우는 A와 B의 x, y, r이 모두 같아야 성립된다. 이땐 -1을 출력한다.
- x1과 x2, y1과 y2가 같지만, r1과 r2가 다르다면 0을 출력한다.
- 한 점에서 만나는 경우는, r1 + r2가 A, B 사이의 거리와 같은 경우다. 피타고라스 정리를 이용하면 A와 B 사이의 거리를 구할 수 있다. 다만 sqrt 연산은 부하가 크니, 제곱된 상태 그대로 연산을 수행한다.
- 두 점에서 만나는 경우는 r1 + r2가 둘 사이의 거리보다 짧을 경우, 만나지 않는 경우는 r1 + r2가 둘 사이의 거리보다 길 경우다. 4가지 경우에 맞춰 코딩해 보자.
1. 있을 수 있는 좌표의 수를 반환하는 함수를 생성한다.
2. x1, y1, r1이 각각 x2, y2, r2와 모두 같다면 -1을 반환한다.
3. x1, y1이 각각 x2, y2와 같고, r1과 r2가 다르다면 0을 반환한다.
4. |x1 - x2|^2 + |y1 - y2|^2로 A, B사이 거리의 제곱을 구한다. r1 + r2의 제곱과 같다면 1을 반환한다.
5. 위 경우와 다르다면 2를 반환한다.
코드
#include <iostream>
#include <cmath>
#include <vector>
int chance(int x1, int y1, int r1, int x2, int y2, int r2) {
if (x1 == x2 && y1 == y2) {
if (r1 == r2) {
return -1;
}
return 0;
}
if (pow(x1 - x2, 2) + pow(y1 - y2, 2) == pow(r1 + r2, 2)) {
return 1;
}
return 2;
}
int main() {
int n;
std::vector<int> answer;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x1, y1, r1, x2, y2, r2;
std::cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;
answer.push_back(chance(x1, y1, r1, x2, y2, r2));
}
for (auto i : answer) {
std::cout << i << '\n';
}
return 0;
}
결과
결과는 실패. 입출력엔 문제가 없었다.
2. 두 번째 풀이
사고 과정
- 위 과정에서 빼먹은 사례가 있을까?
- 지금까지 난 A와 B 사이에만 마린이 있다고 가정했었다. 만약 A의 원 안에 B의 원이 포함된다면?
- A와 B의 거리가 |r1 - r2|와 같다면 한 점에서, 보다 크다면 두 점에서, 보다 작다면 만나지 않을 것이다.
- 이 사례들도 추가하여 코드를 작성해 보자.
코드
#include <iostream>
#include <cmath>
#include <vector>
int chance(double x1, double y1, double r1, double x2, double y2, double r2) {
// 조규현과 백승환이 같은 위치일 때
if (x1 == x2 && y1 == y2) {
// 거리가 같다면 무한대
if (r1 == r2) {
return -1;
}
// 다르면 0
return 0;
}
// 하나의 원이 다른 하나를 포함할 때, 둘 사이의 거리가 두 원의 반지름 차보다
int dis = (pow(x1 - x2, 2) + pow(y1 - y2, 2)) - pow(r1 - r2, 2);
// 클 때 (두 점에서 만남)
if (dis > 0) {
return 2;
}
// 같을 때 (한 점에서 만남)
else if (dis == 0) {
return 1;
}
// 작을 때 (원이 겹치지 않음)
else {
return 0;
}
// 둘 사이의 거리가 두 원의 반지름 합보다
dis = (pow(x1 - x2, 2) + pow(y1 - y2, 2)) - pow(r1 + r2, 2);
// 클 때 (원이 겹치지 않음)
if (dis > 0) {
return 0;
}
// 같을 때 (한 점에서 만남)
else if (dis == 0) {
return 1;
}
// 작을 때 (두 점에서 만남)
else {
return 2;
}
}
int main() {
int n;
std::vector<int> answer;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x1, y1, r1, x2, y2, r2;
std::cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;
answer.push_back(chance(x1, y1, r1, x2, y2, r2));
}
for (auto i : answer) {
std::cout << i << '\n';
}
return 0;
}
결과
두 경우를 나누어 코딩해 봤지만, 여전히 실패였다. 그나마 시간 초과가 아닌 점은 다행이다.
3. 마지막 풀이
사고 과정
- 차분히 내 코드를 다시 살펴보니, 저 코드엔 큰 문제가 있었다.
- A, B 사이의 거리를 r1 - r2와 비교한 후, 반드시 셋 중 하나로 반환되어 r1 + r2와 비교하지 않는 문제다.
- 이 문제를 해결하려면 r1 - r2와 r1 + r2를 동시에 비교해야 했다.
- 분기를 제대로 정리하기 위해, 수직선을 그려보았다.
|r1 - r2| < A, B 사이 거리 < |r1 + r2|일 때 두 점, 둘 중 하나와 같을 때 한 점, |r1 - r2|보다 작거나 |r1 + r2|보다 크면 만나지 않았다.
다시 코드로 정리해보자.
코드
#include <iostream>
#include <cmath>
#include <vector>
int chance(double x1, double y1, double r1, double x2, double y2, double r2) {
// 조규현과 백승환이 같은 위치일 때
if (x1 == x2 && y1 == y2) {
// 거리가 같다면 무한대
if (r1 == r2) {
return -1;
}
// 다르면 0
return 0;
}
// 둘 사이의 거리
int dis = pow(x1 - x2, 2) + pow(y1 - y2, 2);
if (pow(r1 - r2, 2) < dis && dis < pow(r1 + r2, 2)) {
return 2;
}
// 같을 때 (한 점에서 만남)
else if (dis == pow(r1 - r2, 2) || dis == pow(r1 + r2, 2)) {
return 1;
}
else {
return 0;
}
}
int main() {
int n;
std::vector<int> answer;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x1, y1, r1, x2, y2, r2;
std::cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;
answer.push_back(chance(x1, y1, r1, x2, y2, r2));
}
for (auto i : answer) {
std::cout << i << '\n';
}
return 0;
}
결과
깔끔하게 문제를 풀었다.
개선
사고 과정
- 두 점에서 만나는 분기일 때, pow(r1 - r2, 2) < dis && dis < pow(r1 + r2, 2) 대신 pow(r1 - r2, 2) < dis < pow(r1 + r2, 2)로 줄일 수 있을 것 같다.
코드
#include <iostream>
#include <cmath>
#include <vector>
int chance(double x1, double y1, double r1, double x2, double y2, double r2) {
if (x1 == x2 && y1 == y2) {
if (r1 == r2) {
return -1;
}
return 0;
}
int dis = pow(x1 - x2, 2) + pow(y1 - y2, 2);
if (pow(r1 - r2, 2) < dis < pow(r1 + r2, 2)) {
return 2;
}
else if (dis == pow(r1 - r2, 2) || dis == pow(r1 + r2, 2)) {
return 1;
}
else {
return 0;
}
}
int main() {
int n;
std::vector<int> answer;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x1, y1, r1, x2, y2, r2;
std::cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;
answer.push_back(chance(x1, y1, r1, x2, y2, r2));
}
for (auto i : answer) {
std::cout << i << '\n';
}
return 0;
}
결과
결과는 실패. 왜 실패했을까?
구글링 속도를 100배 줄여준다는 chatGPT에게 관련 질문을 해보았다. GPT의 답변은 아래와 같았다.
a = 5로 초기화한 경우, 조건식 내부에 0 < a < 10을 작성한다면 다음 과정을 거친다. 컴파일러는 우선 0 < a를 실행한다. 이는 참이므로 1(true)을 반환한다. 그럼 식은 다시 1 < 10이 되고, 이는 거짓이 되어 조건문 내용이 실행되지 않는다. 5는 0보다 크고 10보다 작음에도 말이다.
이를 검증하기 위해 추가적으로 구글링해본 결과, 백준에서 다음 글을 찾을 수 있었다.
이렇게 몰랐던 사실을 하나 더 알게 되었다.
가장 효율적인 코드
https://www.acmicpc.net/source/4307344
laywolf님의 코드다.
내 코드와 비교
1. cin 대신 scanf를 쓰셨다.
2. x, y, r이 모두 같은 경우 -1을 반환하는 건 똑같지만, 이후 r만 다른 경우는 생략하셨다.
3. x1, y1, r1, x2, y2, r2는 long 형으로, d(거리)는 float 형으로 선언하셨다.
4. 거리를 계산할 때 제곱근으로 구하셨다. 다만 여기서도 sqrt 함수를 쓰는 대신 pow(,,0.5f)를 사용하셨다.
5. else if 조건식에서 ||을 줄내림하여 가독성을 높이셨다.
6. 함수를 따로 구현하지 않고, for문 내에서 입력 후 출력 사이클을 반복시켰다.
난 입력값을 모두 받은 후 출력값을 한 번에 출력하는 줄 알았는데, 이 코드를 보니 그게 아닌 듯하다. 이건 내가 백준 스타일을 잘 몰라서 생긴 문제다.
실험
입력 후 바로 출력하는 식으로도 통과가 될까?
코드
#include <iostream>
#include <cmath>
#include <vector>
int chance(double x1, double y1, double r1, double x2, double y2, double r2) {
if (x1 == x2 && y1 == y2) {
if (r1 == r2) {
return -1;
}
return 0;
}
int dis = pow(x1 - x2, 2) + pow(y1 - y2, 2);
if (pow(r1 - r2, 2) < dis && dis < pow(r1 + r2, 2)) {
return 2;
}
else if (dis == pow(r1 - r2, 2) || dis == pow(r1 + r2, 2)) {
return 1;
}
else {
return 0;
}
}
int main() {
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x1, y1, r1, x2, y2, r2;
std::cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;
std::cout << chance(x1, y1, r1, x2, y2, r2) << '\n';
}
return 0;
}
결과
충분히 가능했다. 이러면 굳이 출력값을 저장해뒀다가 내보내지 않아도 된다
강평
경우의 수를 잘 찾는다면 쉽게 풀리는 문제였다. 푸는 과정에서 새로운 지식(0 < a < 10)을 획득했고, 백준에도 좀 더 익숙해졌다.
'코딩테스트 > 백준' 카테고리의 다른 글
[백준/C++] 1049번. 기타줄 (0) | 2023.08.19 |
---|---|
[백준/C++] 1012번. 유기농 배추 (0) | 2023.08.19 |
[백준/C++] 1065번. 한수 (0) | 2023.08.18 |
[백준/C++] 1032번. 명령 프롬프트 (0) | 2023.08.15 |
[백준/C++] 1026번. 보물 (0) | 2023.08.14 |