Skip to content

Conversation

@minor7295
Copy link
Owner

Summary

BOJ 2166 다각형의 면적 문제를 신발끈 공식(Shoelace Formula)을 활용한 계산 기하학 알고리즘으로 해결하고, 문제 분석부터 알고리즘 선택, 구현까지의 전체 사고 과정을 문서화했습니다. 특히 선형대수를 모르는 사람도 이해하기 쉽게 설명하는 것을 목표로, 기하학적 직관을 중심으로 신발끈 공식을 설명했습니다.

주요 구현 내용:

  • 신발끈 공식 활용: 각 변과 원점을 연결한 사다리꼴의 넓이를 모두 더하여 다각형 면적 계산
  • 정수 연산: 부동소수점 오차 없이 정확한 계산
  • 오버플로우 방지: long 타입 사용으로 좌표 범위 내에서 안전한 계산
  • 절댓값 처리: 시계/반시계 방향과 무관하게 항상 양수 면적 반환
  • 시간 복잡도: O(N) - N개의 점을 한 번씩만 순회
  • 공간 복잡도: O(N) - N개의 점의 좌표 저장

추가된 파일:

  • computational-geometry/boj-2166/1.analysis.md: 문제 분석 및 핵심 제약 조건 (120줄)
  • computational-geometry/boj-2166/2.algorithm.md: 신발끈 공식 알고리즘 및 면적 계산 방법 (346줄)
  • computational-geometry/boj-2166/3.reasoning.md: 알고리즘 선택 근거 및 코드 설계 (180줄)
  • computational-geometry/boj-2166/Main.java: 신발끈 공식 기반 해답 코드 (53줄)

주요 판단 및 구현 세부사항

1. 신발끈 공식을 활용한 면적 계산: 왜 삼각형 분할이나 다른 방법이 아닌가?

배경 및 문제 상황:
다각형의 면적을 구하는 문제에서, 직관적으로는 다각형을 여러 개의 삼각형으로 나누어 각각의 넓이를 더하는 방법을 생각할 수 있습니다. 하지만 이러한 방법은 삼각형으로 나누는 로직이 복잡하고, 구현이 어려울 수 있습니다.

해결 방안:
신발끈 공식(Shoelace Formula)을 활용하여 각 변에 대해 간단한 계산만으로 면적을 구합니다.

핵심 사고 과정:

  1. 문제의식: 다각형의 면적을 어떻게 구할까?
  2. 전략: 신발끈 공식 활용
    • 각 변과 원점을 연결한 사다리꼴의 넓이를 모두 더함
    • 각 변에 대해 x[i] * y[j] - x[j] * y[i] 계산
    • 모두 더한 후 절댓값을 구하고 2로 나눔
  3. 효율성: O(N) 시간 복잡도로 매우 빠름

구현 코드:

// computational-geometry/boj-2166/Main.java
static double polygonArea(long[] x, long[] y, int n) {
    long area = 0;
    
    // 각 변에 대해 계산
    for (int i = 0; i < n; i++) {
        int j = (i + 1) % n;  // 다음 점 (마지막 점은 첫 번째 점과 연결)
        // 신발끈 공식: x[i] * y[j] - x[j] * y[i]
        area += x[i] * y[j] - x[j] * y[i];
    }
    
    // 절댓값을 구하고 2로 나눔
    return Math.abs(area) / 2.0;
}

다른 접근 방식과의 비교:

1. 삼각형으로 분할:

// 다각형을 여러 삼각형으로 나누어 각각의 넓이를 더함
  • 복잡한 분할 로직: 삼각형으로 나누는 알고리즘이 복잡함
  • 구현 난이도: 삼각형 분할 로직 구현이 어려움
  • 시간 복잡도: 삼각형 개수만큼 계산 필요

2. 사다리꼴로 분할:

// 각 변과 원점을 연결한 사다리꼴의 넓이를 계산
  • ⚠️ 구현 복잡도: 사다리꼴 넓이 계산 로직 필요
  • ⚠️ 중간 단계: 신발끈 공식과 유사하지만 더 복잡

3. 신발끈 공식 (현재 방식) ✅:

area += x[i] * y[j] - x[j] * y[i];
  • 구현이 매우 간단: 복잡한 분할 로직 없이 공식만 사용
  • O(N) 시간 복잡도: 각 점을 한 번씩만 방문
  • 정확함: 수학적으로 정확한 결과
  • 표준 방법: 계산 기하학에서 널리 사용되는 방법

고민한 점:

  • 처음에는 삼각형으로 나누는 방법이 직관적으로 보일 수 있습니다.
  • 하지만 신발끈 공식은 구현이 간단하면서도 정확하고 효율적입니다.
  • 신발끈 공식은 계산 기하학의 표준 방법으로, 다각형 면적 계산에서 널리 사용됩니다.

2. 선형대수 지식 없이 이해하기 쉽게 설명: 기하학적 직관 중심

배경 및 문제 상황:
신발끈 공식은 외적(Cross Product)을 기반으로 하지만, 선형대수를 모르는 사람에게는 이해하기 어려울 수 있습니다. 외적이나 행렬식 같은 개념을 최소화하고, 기하학적 직관을 중심으로 설명해야 합니다.

해결 방안:
"사다리꼴 넓이의 합"이라는 기하학적 직관을 중심으로 설명하고, 외적 개념은 선택적으로 설명합니다.

핵심 설명 방식:

  1. 기하학적 직관: 각 변과 원점을 연결한 사다리꼴의 넓이를 모두 더함
  2. 시각적 비유: "신발끈을 묶는 것처럼" 좌표를 교차하여 계산
  3. 구체적 예시: 실제 좌표값을 사용한 단계별 계산 과정
  4. 선형대수 최소화: 외적 개념은 선택적으로 설명, 공식만 외워도 사용 가능

문서 구조:

  • 1.analysis.md: 문제 분석 및 예시
  • 2.algorithm.md:
    • 신발끈 공식의 기하학적 의미 (사다리꼴 넓이의 합)
    • 구체적인 수치 예시 (정사각형, 삼각형)
    • 선형대수 배경은 선택적 설명
  • 3.reasoning.md: 알고리즘 선택 근거

고민한 점:

  • 선형대수 지식이 있는 사람에게는 외적 개념을 설명하는 것이 도움이 됩니다.
  • 하지만 선형대수 지식이 없는 사람도 이해할 수 있도록 기하학적 직관을 중심으로 설명했습니다.
  • 공식만 외워서 사용해도 충분하도록 구성했습니다.

3. 오버플로우 방지: 왜 long 타입을 사용하는가?

배경 및 문제 상황:
좌표 범위가 -100,000 ~ 100,000이므로, 신발끈 공식 계산 시 최대값은 100,000 × 100,000 = 10,000,000,000입니다. int 범위는 -2,147,483,648 ~ 2,147,483,647이므로 오버플로우가 발생합니다.

해결 방안:
long 타입을 사용하여 오버플로우를 방지합니다.

구체적인 계산:

좌표 범위: -100,000 ~ 100,000
신발끈 공식 계산: x[i] * y[j] - x[j] * y[i]
최대값: 100,000 × 100,000 = 10,000,000,000
int 범위: -2,147,483,648 ~ 2,147,483,647
→ int 범위를 초과하므로 long 타입 필수!

구현 코드:

// ❌ 오버플로우 발생
int area = 0;
area += x[i] * y[j] - x[j] * y[i];

// ✅ 안전한 코드
long area = 0;
area += x[i] * y[j] - x[j] * y[i];

고민한 점:

  • 이 문제에서는 long 타입이 필수입니다.
  • 계산 기하학 문제에서는 좌표 범위가 클 수 있으므로, 오버플로우를 항상 주의해야 합니다.
  • 오버플로우는 디버깅하기 어려운 버그를 만들 수 있으므로, 예방이 중요합니다.

4. 절댓값 사용: 왜 절댓값을 사용하는가?

배경 및 문제 상황:
신발끈 공식의 결과는 다각형이 시계 방향이면 음수, 반시계 방향이면 양수가 나옵니다. 하지만 면적은 항상 양수여야 합니다.

해결 방안:
절댓값을 구한 후 2로 나눕니다.

구체적인 예시:

시계 방향 다각형:
  area = -200
  |area| = 200
  면적 = 200 / 2 = 100 ✅

반시계 방향 다각형:
  area = 200
  |area| = 200
  면적 = 200 / 2 = 100 ✅

→ 방향과 무관하게 같은 면적!

구현 코드:

// 절댓값을 구하고 2로 나눔
return Math.abs(area) / 2.0;

고민한 점:

  • 다각형의 방향(시계/반시계)은 면적과 무관합니다.
  • 절댓값을 사용하여 항상 양수 면적을 반환하도록 했습니다.
  • 이는 신발끈 공식의 표준 사용법입니다.

5. 마지막 점과 첫 번째 점 연결: 순환 구조 처리

배경 및 문제 상황:
다각형은 닫힌 도형이므로, 마지막 점 P[N-1]과 첫 번째 점 P[0]도 연결되어야 합니다.

해결 방안:
모듈로 연산을 사용하여 순환 구조를 만듭니다.

구현 코드:

for (int i = 0; i < n; i++) {
    int j = (i + 1) % n;  // 다음 점 (마지막 점은 첫 번째 점과 연결)
    area += x[i] * y[j] - x[j] * y[i];
}

예시:

점: P0, P1, P2, P3
변: P0→P1, P1→P2, P2→P3, P3→P0 (마지막 변!)

고민한 점:

  • 다각형은 닫힌 도형이므로 마지막 점과 첫 번째 점을 연결해야 합니다.
  • 모듈로 연산 (i + 1) % n을 사용하여 간단하게 처리했습니다.
  • 이는 다각형 관련 문제에서 자주 사용되는 패턴입니다.

구현 세부사항

1. 신발끈 공식 구현

핵심 로직:

  1. 각 변에 대해 계산: 다각형의 각 변 P[i] → P[i+1]에 대해 x[i] * y[j] - x[j] * y[i] 계산
  2. 모두 더하기: 계산한 값들을 모두 더함
  3. 절댓값과 나누기: 결과의 절댓값을 구하고 2로 나눔

관련 코드:

// computational-geometry/boj-2166/Main.java
static double polygonArea(long[] x, long[] y, int n) {
    long area = 0;
    
    // 각 변에 대해 계산
    for (int i = 0; i < n; i++) {
        int j = (i + 1) % n;  // 다음 점 (마지막 점은 첫 번째 점과 연결)
        // 신발끈 공식: x[i] * y[j] - x[j] * y[i]
        area += x[i] * y[j] - x[j] * y[i];
    }
    
    // 절댓값을 구하고 2로 나눔
    return Math.abs(area) / 2.0;
}

예시 (정사각형: (0,0), (0,10), (10,10), (10,0)):

i=0: j=1, area += 0*10 - 0*0 = 0
i=1: j=2, area += 0*10 - 10*10 = -100
i=2: j=3, area += 10*0 - 10*10 = -100
i=3: j=0, area += 10*0 - 0*0 = 0

총합: 0 + (-100) + (-100) + 0 = -200
절댓값: 200
나누기 2: 200 / 2 = 100 ✅

2. 시간 복잡도 분석

전체 시간 복잡도: O(N)

근거:

  • N개의 점을 입력받음: O(N)
  • 각 점에 대해 한 번씩 계산: O(N)
  • 출력: O(1)

실제 연산 횟수:

입력: N줄 × 2개 정수 = 2N개 정수 읽기
면적 계산: N번의 곱셈과 뺄셈
출력: 1개 실수

N = 10,000일 때:
총 연산 횟수: 약 20,000번 미만
예상 실행 시간: 거의 0초 ✅ (매우 빠름)

3. 공간 복잡도 분석

전체 공간 복잡도: O(N)

근거:

  • 입력 저장: N개의 점의 좌표 저장 (2N개 long 변수)
  • 계산 변수: 면적 계산에 필요한 변수 (1개 long)
  • 전체 공간: O(N)

실제 메모리 사용량:

입력 변수: N개 점 × 2개 좌표 × 8 bytes (long) = 16N bytes
계산 변수: 1개 long = 8 bytes

N = 10,000일 때: 약 160,008 bytes ≈ 160 KB

메모리 제한과의 비교:

  • 문제의 메모리 제한: 128 MB
  • 사용 메모리: 약 160 KB
  • 사용률: 160 KB / 128 MB ≈ 0.12%
  • 결론: 메모리 제한 내에서 충분히 여유 있음 ✅

4. 반올림 처리

문제 요구사항:

  • 소수점 아래 둘째 자리에서 반올림하여 첫째 자리까지 출력

해결 방안:
printf("%.1f", ...)를 사용하여 자동으로 반올림 처리합니다.

구현 코드:

double area = polygonArea(x, y, n);
System.out.printf("%.1f\n", area);

@minor7295 minor7295 marked this pull request as draft January 22, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants