일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- cout.precision
- 코딩테스트준비
- 항해99
- 스트릭
- 수행 시간
- timeval
- fixed
- 백준 스트릭
- 코드잇
- 파이썬
- Til
- 리뷰어
- 오블완
- 개발자취업
- 7기
- 코드잇 Python 풀스택 개발자
- 백준 1000문제
- 프리코스
- solved.ac
- 백준
- 소수점 출력
- 우아한테크코스
- 티스토리챌린지
- 길벗
- 백준 문제 출제
- 코드잇 JavaScript 프론트엔드 개발자
- 개발자
- 99클럽
- 코드잇 강의
- 우테코
- Today
- Total
BE THE DEVELOPER
99클럽 코테 스터디 2일차 TIL: 이분 탐색의 범위 본문
오늘의 문제 | 11561. 징검다리
우선 처음 읽었을 때 그리디로 해결해야 하는 생각이 들었다. 정해진 숫자가 없고 머리로는 대충 때려맞춰서 풀 수 있는 문제들은 대체로 그리디인 적이 많았다고 생각했다. 우선 규칙이 이해가 안가는 부분이 있어서 문제부터 꼼꼼히 읽었다.
- 징검다리는 N번까지 있고, 시작점을 아무거나 선택할 수 있음.
- 최대 징검다리 수를 구하는 것으로, 시작하자마자 바로 N번을 밟아버리는 경우의 수인 1번이 최소치임.
- 입력값이 10^16이므로 정상적인 방법으로 해결은 불가능(브루트 포스 불가능)
- 따라서 지난 번처럼 이분 탐색을 이용해야겠다는 생각. 실제로 알고리즘 분류도 이분 탐색. </aside>
아이디어 정리
우선 우리가 찾아야 하는건 최대 횟수이다. 바로 전 점프보다 더 많이 뛰어야 하므로, 시작할 때 1번 뛰고 그다음 2번, 3번 뛰다가… 마지막에 N번 징검다리를 넘기지 않는 선에서 최선의 횟수를 구해야 함.
즉, 마지막 점프는 N번에 딱 도착하도록 뛰면 된다는 것.
문제에서 N이 10^16까지인 이유를 고려해보면 약간 문제가 있다. 2^16이었으면 최대 숫자가 2^32에 준하는 숫자가 등장하기에 int형에 맞는 범주이겠지만, 최대로 등장하는 10^32가 아닌가? 생각보다 숫자가 너무 커져서 오류가 생길거 같다는 생각이 들었다.
수의 범위를 고려하지 않고는 결과가 제대로 나오는지를 확인하기로 했다.
# 정답 코드
for i in range(int(input())):
n=int(input())
left=0
right=n
mid=1
while 1:
if left>=right: break
mid=(left+right+1)//2
if (mid**2+mid)//2<n:
left=mid
elif (mid**2+mid)//2>n:
mid-=1
right=mid
else: break
print(mid)
# 오답 코드
for i in range(int(input())):
n=int(input())
left=0
right=n
mid=1
while 1:
if left>=right-1: break
mid=(left+right)//2
if (mid**2+mid)/2<n:
left=mid
elif (mid**2+mid)/2>n:
mid-=1
right=mid
else: break
print(mid)
골 때린다. 어떻게 맞춘건지 감이 잡히지 않는다. 저번 문제처럼 그냥 1 단위로 왔다갔다 해보니까 정답이 나오게 되었다. 어이가 없는 점은 두가지가 있다.
왜 오버플로우가 나지 않았는가?
물론 파이썬이기에 그럴 수 있다. 하지마 그럼에도 숫자가 상당히 커지기에 +1 연산에서 오차가 발생할 수 있을텐데, 왜 오류가 나지 않았는지를 알아보려 한다.
long long을 사용할 경우에는 10^16까지는 무리없이 입력 받을 수 있다. 검색해본 결과 unsigned long long을 쓰면 무리없이 C++에서도 해결할 수 있다고 한다. 사실 나는 이분 탐색을 N까지 돌렸지만, 좀만 생각해보면 루트N 근처 정도의 숫자가 최선이라는 사실을 알아낼 수 있다. 그렇다면 mid를 제곱해도 unsigned long long을 넘을 일은 없어보인다.
결론: 파이썬이 파이썬했다.
mid값을 구할 때 +1을 하는게 무슨 소용인가
이건 내 if문 로직과 관련이 있을 것으로 보인다. mid를 구할 때 +1을 하게 되면 left와 right의 중간값의 반올림 값으로 구해진다. 그렇다면 원래 오답 코드에서 left와 right를 비교할 때, left가 계속 작은 상황을 방지할 수 있다는게 장점인듯 하다.
로직상으로 볼 때, N보다 큰 케이스에서는 정답이 존재할 가능성이 없으므로 right를 mid-1로 갱신해주는데, N보다 작은 케이스에서는 정답이 존재할 수 있으므로 left를 mid로 갱신해주었다. 이 과정에서 left와 right가 1 차이날 경우 생기는 문제를 잡아주는 듯하다.
오늘의 회고
확실히 이분 탐색을 (그것도 숫자를 찾아나설 때) 확실한 근거를 갖고 접근하기는 어렵다고 생각한다. 대략적인 값은 알아낼 수 있으나 이걸 일반화시키기 위한 작업이 오래 걸린다고 느꼈다.
그래도 어느 정도 이분 탐색의 감을 잡은 듯하다. 정답을 포함할 수 있는 가능성이 있는가?를 기준으로 범위를 좁혀나가는 방식으로 생각하면 될 듯하다.
'일기장 > 항해99클럽 4기' 카테고리의 다른 글
99클럽 코테 스터디 6일차 TIL: 이분 탐색 파이썬화 (0) | 2024.11.02 |
---|---|
99클럽 코테 스터디 5일차 TIL: 너비 우선 탐색 (0) | 2024.11.02 |
99클럽 코테 스터디 4일차 TIL: 깊이 우선 탐색의 재귀 깊이 (0) | 2024.10.31 |
99클럽 코테 스터디 3일차 TIL: 이분 탐색 반환값 (0) | 2024.10.30 |
99클럽 코테 스터디 1일차 TIL: 브루트포스와 이분 탐색 (0) | 2024.10.28 |