1.4일차 복습

최대공약수 : 유클리드 호재법

동치 역원 구하기 : 확장 유클리드 호제법(?)

소수구하기 : 나눠서 구한다(제곱근 지우기), 에라토스테네스의 체

오일러 피 함수 : 1~N-1까지 

경우의 수 : 순열, 조합(조합은 동적 계획법으로 해결)

 

2. 다리놓기 1010

[문제]

1.도시를 동서로 나누는 큰 일직선 모양의 강이 흐르고 있다. 동서를 잇는 적합한 곳(사이트)에 다리를 짓고자 한다.

2.서쪽에는 N개의 사이트, 동쪽에는 M개의 사이트가 있다.(N<=M)

3.한 사이트에는 한 개의 다리만 연결 가능하며, 서쪽 사이트 개수만큼 다리를 지을 수 있는 경우의 수를 구하여라.

=>결국 오른쪽 M개의 점 중에서 N개를 선택하면, 순서대로 왼쪽에 연결된다고 생각할 수 있다.

 

[풀이]

지난번에 조합을 한 번 쭉 정리한 적이 있다. 하지만 암기하지는 않았었다. 그래서 기억이 나지 않는다.

정리한 노트를 보고 암기를 오늘 제대로 해야겠다.

암기하기 전에 예상되는 풀이 방법으로는,

 

-일반식을 새롭게 세웠었다.

-일반식을 새로 가져오는 것 외에는, 라이브러리를 사용하는 방법과, 메모이제이션 방법이 있었던 것 같다.

 

[정답코드]

def comb(n,k):
    if k > n:
        return 0
    if k==0 or n==k:
        return 1
    
    #k가 n/2보다 큰 경우, nCk=nC(n-k)와 같음. 더 작은 수로 바꿔주는 과정
    if k > n/2 :
        k = n-k

    result=1
    for r in range(k): #r-1까지 돌 것임
        result = result * (n-r) // (r+1)

    return result

c=int(input())
for _ in range(c):
  r, n = map(int, input().split())
  print(comb(n,r))

 

[팁]

가장 큰 수인 M도 한 번 찍어봐야한다. overflow 방지를 위해서!

최대 크기가 10만으로 주어지면, nlogn 알고리즘을 사용하면 된다.

3. 순열의 순서 1722(골드5)

[문제]

1.임의의 순열(N!)은 정렬할 수 있다. 예를 들어 N=3인 경우, 6가지 경우의 수가 있다. 6가지 경우의 수를 나열하면 앞에서부터 작은 것이 순서상 앞선다.

2.N이 주어지면 , 두 가지 소문제 중 하나를 풀어야한다.

-문제 번호 1) K를 입력받고, N!에서의 K번째 순열을 구한다.

-문제 번호 2) 임의의 순열 N개를 입력받고, 이 순열이 몇 번째 순열인지를 출력하기.

 

4.사전 1256(골드2)

[문제]

-사전에 수록되어있는 모든 문자열은 N개의 "a"와 M개의 "z"로 이루어져있다. 다른 문자는 없고, 알파벳 순서대로 수록되어있다.

-규완이는 사전을 완성했고, 동호는 사전을 완성하지 못했다. 동호는 규완이가 자리를 비운 사이에 몰래 사전을 보려고 하기 때문에 문자열 하나만 찾을 수 있다.

-N과 M이 주어졌을 때, 규완이의 사전에서 K번째 문자열이 무엇인지 구하는 프로그램을 작성하시오.

-입력 : 첫째 줄에 세 정수 N,M,K가 주어진다.

-출력 : 규완이의 사전에서 K 번째 문자열을 출력한다. 사전에 등록된 문자열 개수가 K보다 작으면 -1을 출력한다.

 

[풀이]

-경우의 수만 따져가며, 앞에서부터 문자열을 채우는 것이 핵심이다.

-n,m개의 문자로 구성할 수 있는 문자열의 수는 math.comb(n+m,n)이다. a의 자리를 결정하면, 나머지를 z로 채우기만 하면 된다.

-n,m에 대해 (n-1)개의 a와, m개의 z로 만들 수 있는 문자열의 수인 math.comb(n+m-1,n-1)보다 k의 값이 크다면, 맨 앞 

유독 이해가 안되는 문제...

from math import comb as c

a,b,i=map(int,input().split())
s=["z"]*(a+b)

if c(a+b,a)<i:
  print(-1)
else:
  ans=""
  #과정을 반복하여 n,m 둘 중 하나가 0이되거나 k의 값이 0이될 때까지 반복하며 문자열을 채워나가고, 남은 문자가 있다면 그 개수만큼 뒤에 붙여준다.
  while i and (a and b):
    if i > c(a+b-1,a-1): #n-1개의 a와 m개의 z로 만들 수 있는 수 c(a+b-1,a-1)보다 k가 크면, 맨 앞 문자는 a이므로
      ans+="z"
      i-=c(a+b-1,a-1)
      b-=1
    else:
      ans+="a"
      a-=1
  print(ans+"a"*a+"z"*b)

 

사전 문제랑 순열의 순서 문제 이해가 잘 안됨. 다시 봐야함 ...

직접 시간 들여서 풀어봐야하는 듯.

 

5. 6549 히스토그램에서 가장 큰 직사각형

[풀이]

https://codable.tistory.com/1

위의 블로그에 설명이 상세하게 적혀있어서, 참고하며 공부를 해보려고 한다.

 

1) 분할 정복 풀이 : 중앙값을 기준으로, 양쪽으로 넓혀가며 최댓값을 찾는 작업.

처음 중간값인 8에서 시작하고, 왼쪽과 오른쪽 중 더 높이가 높은쪽으로 확장한다. 그리고 이때의 넓이값도 적어둔다.

또 해당 직사각형과 맞닿아있는 히스토그램 중 더 높은 쪽으로 확장한다. 확장하고나서 넓이를 갱신해준다.

최종적으로 분할 기준을 포함하는 최댓값, 왼쪽 범위의 최댓값, 오른쪽 범위의 최댓값을 모두 구하고, 이 셋을 비교하여 가장 큰 값을 최댓값으로 잡아준다.

 

어려웠지만 코드 이해 안되는 부분은 손으로 쓰고, 직접 값 넣어서 생각하니 이해가 되었다. 까먹기 전에 복습 혹은 영상을 찍어두면 도움이 될 것 같다.

import sys
input = sys.stdin.readline

res=[]

def divide_and_conquer(histogram, start, end):
  #end=start 같으면, 복잡한 과정 거칠 것 없이 그냥 histogram[end] 또는 histogram[start]다.
  if end == start:
    return histogram[end]
  #만약 end와 start가 바로 한 칸 차이면(붙어 있으면)
  elif end - start == 1:
    #게다가 end보다, start가 더 높이가 높을 경우
    if histogram[end] < histogram[start]:
      #복잡한 과정 할 것 없이,end값에 두 배를 한 것과, start 값 중 더 큰 값 비교
      return max(2*histogram[end], histogram[start])
    else:
      #start 값에 두 배를 한 것과, end 값 중 더 큰 값 비교
      return max(2*histogram[start], histogram[end])

  mid = ( start + end ) //2
  left_area = divide_and_conquer(histogram, start, mid)
  right_area = divide_and_conquer(histogram, mid+1, end)
  left=mid-1
  right=mid+1

  #mid_area : 기존 넓이를 기록할 변수 초기화
  mid_area = histogram[mid]
  #now_height : 현재 높이를 기록할 변수 초기화. 시작은 중간값
  now_height = histogram[mid]

  while start <= left and right <= end:
    #왼쪽과 오른쪽 중 오른쪽 히스토그램 높이가 더 높으면
    if histogram[left] < histogram[right]:
      #'오른쪽 히스토그램 높이' 보다, '현재 높이'가 더 높으므로
      if histogram[right] < now_height:
        #'현재 높이'를 더 낮은 '오른쪽 히스토그램 높이'로 설정한다.
        now_height = histogram[right]
      #그리고 현재까지의 넓이(mid_area)와, 새롭게 지정된 높이(now_height)에 대한 넓이를 비교하여
      #더 높은 것을 새로운 mid_area로 지정한다.
      mid_area = max(mid_area, now_height * (right-left))
      #그리고 오른쪽으로 한 칸 범위를 높인다.
      right +=1
    #왼쪽과 오른쪽 중 왼쪽 히스토그램 높이가 더 높으면
    else:
      if histogram[left] < now_height:
        now_height = histogram[left]
      mid_area = max(mid_area, now_height*(right - left))
      left -= 1
  
  #right는 범위를 벗어났고, 조건이 없어졌다. 왼쪽으로 확장할 공간만 남은 상황.
  while start <= left:
    if histogram[left] < now_height:
      now_height = histogram[left]
    mid_area = max(mid_area, now_height * ( right - left ))
    left -=1
  
  while right <= end:
    if histogram[right] < now_height:
      now_height = histogram[right]
    mid_area = max(mid_area, now_height * ( right - left))
    right+=1
  
  return max(left_area, right_area, mid_area)



res=[]

while True:
  #직사각형의 수, 직사각형의 높이들 주어짐 ex) 7 2 1 4 5 1 3 3
  histogram = list(map(int, input().split()))

  n = histogram[0]
  #입력 마지막 시에 종료 조건
  if n ==0:
    break

  #1부터 넘겨주어, 0번째인 값은 해당되지 않도록 함. 
  res.append(divide_and_conquer(histogram, 1, n))

for i in res:
  print(i)​

 

6. 가장 긴 증가하는 부분 수열 11053

[문제]

가장 긴 증가하는 부분 수열의 길이를 구하는 문제.

예를 들어 [10, 20, 10, 30, 20, 50]이 주어지면,

[10, 20, 10, 30, 20, 50]

 

[팁]

-최대수가 1,000으로 주어지면, n^2으로 해결할 수 있다.

-최장증가부분수열(LIS)

-앞에서부터 하나씩 확인한다. 

-나보다 더 앞에 작은게 있어야 붙을 수 있음.

-원리를 계산해봐도 n^2 알고리즘이라는 것을 파악할 수 있음.

 

[풀이]

dp[i] = A[i]를 마지막 원소로 가지는 부분 수열의 최대 길이.

=> A[i]가 어떤 증가부분수열의 마지막 값이 되기 위해서는,

A[i]가 추가되기 전 증가부분수열의 마지막 값이, A[i]보다 작은 값이어야한다.

 

A[i]를 마지막 값으로 가지는 '가장 긴' 증가부분수열의 길이는,

A[i]가 추가될 수 있는 증가부분수열 중 가장 긴 수열의 길이에 1을 더한 값이 된다.

 

n=int(input())
arr = list(map(int, input().split()))

#i가 0일 때 증가하는 최대 부분 수열의 길이는 1이기 때문에, 테이블을 우선 전부 1로 초기화해줌
d = [1]*n

for i in range(1, n): #1부터 n-1번 인덱스까지의 모든 i에 대하여
  for j in range(i): #i보다 작은 j (0부터 i-1까지 = i보다 작은 것 모두)
    if arr[j] < arr[i]: #j의 원소가 i의 원소보다 작은 값이 존재하면!, 즉, 부분적으로 증가한다면
      d[i] = max(d[i], d[j]+1) #i에서의 최적의 해를 갱신해준다.
print(max(d))

 

문제 원리는 이해가 됐는데, 코드 이해가 덜 되었다. 따로 이해 필요하다. 유튜브에 강의가 존재하므로 확인해봐야겠다.

뭐야 나무위키에 엄청 상세하게 정리가 잘되어있다. 공부해보자.

https://namu.wiki/w/%EC%B5%9C%EC%9E%A5%20%EC%A6%9D%EA%B0%80%20%EB%B6%80%EB%B6%84%20%EC%88%98%EC%97%B4

 

최장 증가 부분 수열

최장 증가 부분 수열 문제는 동적 계획법 으로 풀 수 있는 유명한 알고리즘 문제이다. 정의 어떤 임의의 수열이 주

namu.wiki

 

7. 가장 긴 증가하는 부분 수열2 

이 문제 풀이는 총 세 가지가 존재한다.

1)동적계획법(N^2) = 가장 긴 증가하는 부분 수열1

2)인덱스 트리로 계산 가능 : 이분 탐색보다 코드가 조금 복잡하다.

3)근데 이분탐색이 더 빠르게 코드 짜짐.(강사님 자료에서 설명해주셨던 것. 근데 내용 이해 필요)

 

1 5

2 3

앞에 다음과 같은 예시가 있다. 5뒤에 붙을 수 있는 애는 3뒤에도 붙을 수 있기 때문에, 당연히 2 3을 선택하면 된다.

+ Recent posts