-
Atcoder Beginner Contest 252 후기AtCoder 2022. 5. 29. 16:43
A. ASCII code
특정 숫자(아스키 코드)를 문자로 변환하는 문제이다
import sys n = int(sys.stdin.readline()) print(chr(n))
단순한 구현문제이다. 특정 수열의 최대값들 중에서 특정 인덱스에 속하는 최대값이 있는지만 확인하면 된다
import sys # 입력부 n, k = map(int, sys.stdin.readline().split()) arr = list(map(int, sys.stdin.readline().split())) temp = list(map(int, sys.stdin.readline().split())) # val : 수열의 최대값 val = max(arr) # flag : Takahashi가 싫어하는 음식을 먹을 수 있는 경우가 없다면 False, 있다면 True인 플래그 변수 flag = False for idx, i in enumerate(arr): # 현재 수가 최대값인 수인 경우 if i == val: # 현재 수의 인덱스가 싫어하는 음식의 인덱스인 경우 if idx + 1 in temp: flag = True # 정답 출력 print('No' if not flag else 'Yes')
특정 시간 t마다 임의의 슬롯을 하나 정하여 버튼을 누르면 $(t % 10) + 1$번째의 숫자가 화면에 뜬다. 이 때 모두 같은 숫자가 화면에 뜨게 할 수 있는 경우 중 가장 적은 시간이 얼마인지 묻는 문제이다. 전형적인 그리디 문제이다. 현재 시간이 t라고 하고, 특정 숫자 k로 모두 맞추고 싶을 때, 각 슬롯에서 k의 인덱스가 공식에 따라 맞출 수 있다면 해당 슬롯을 누르면 되고, 그런 슬롯이 하나도 없다면 더 기다려야 한다
import sys n = int(sys.stdin.readline()) arr = [] # cache : 특정 수를 key, 각 슬롯에서 특정 수의 인덱스 리스트를 value로 갖는 dictionary cache = dict().fromkeys([i for i in range(10)]) # vid : 특정 수를 key, 각 슬롯을 방문했는지를 나타내는 boolean 리스트를 value로 갖는 dictionary vis = dict().fromkeys([i for i in range(10)]) # 초기화 for i in cache: cache[i] = [] vis[i] = [] for i in range(n): temp = sys.stdin.readline().rstrip() for j in range(len(temp)): # 각 숫자에 해당하는 key에 인덱스 추가 cache[int(temp[j])].append(j + 1) # 각 숫자에 해당하는 슬롯을 아직 방문하지 않았으니 False 추가 vis[int(temp[j])].append(False) ans = [] # 초기 숫자를 고른다 for i in range(10): now = 0 idx = 0 while True: # 모든 슬롯을 다 방문했다면 무한루프 탈출 if vis[i] == [True] * len(cache[i]): break for j in range(len(cache[i])): # 아직 방문하지 않은 슬롯이 있는 경우 if not vis[i][j]: # 현재 시간에서 방문하면 특정 숫자를 띄울 수 있는 경우 if cache[i][j] == (now % 10) + 1: # 방문 체크 vis[i][j] = True break now += 1 # 각 초기숫자 별 결과 저장 ans.append(now) # 정답 출력 print(min(ans) - 1)
특정 수열이 주어지고, 아래와 같은 조건을 만족하는 인덱스의 세 쌍을 구하는 문제이다
$$\begin{cases}
& 1 < i < j < k \leq N \\
& A_i, A_j, A_k \text{ are distinct}
\end{cases}$$$A_i, A_j, A_k$가 모두 다른 수인 경우를 구해야 하므로, 전체 경우의 수에서 겹치는 경우의 수를 빼주면 문제를 풀 수 있다. 이 때, 겹치는 경우의 수는 세 수가 모두 같거나, 두 수만 같은 경우이다. 따라서, 특정 수가 몇 개 있는지만 저장하는 자료구조가 필요하다. 세 수가 모두 같은 경우라면 단순히 특정 수의 총 갯수에서 3개를 뽑으면 된다. 마찬가지로 두 수가 같은 경우라면 단순히 특정 수의 총 갯수에서 2개를 뽑고, 다른 수에서 하나만 뽑으면 된다.
import sys # calc : nCr을 리턴하는 함수, 이때 num이 n, times가 r이 된다 def calc(num, times): if times == 3: return max(0, num * (num - 1) * (num - 2) // 6) if times == 2: return max(0, num * (num - 1) // 2) return num n = int(sys.stdin.readline()) # total : 전체 인덱스에서 아무거나 3개를 뽑을 때 발생하는 경우의 수 total = calc(n, 3) arr = list(map(int, sys.stdin.readline().split())) # res : arr의 원소값을 key, 그때의 총 원소 갯수를 value로 갖는 dictonary res = dict().fromkeys(arr, 0) # 갯수에 따라 갱신 for i in arr: res[i] += 1 for i in res: # 현재 원소가 수열에서 2번 이상 나온 경우 if res[i] >= 2: # 두 수가 겹치는 경우를 제외 total -= calc(res[i], 2) * (n - res[i]) # 세 수가 모두 겹치는 경우를 제외 total -= calc(res[i], 3) # 정답 출력 print(total)
업솔빙 문제이다. 이후 생각을 많이 했지만 풀이가 떠오르지 않아 에디토리얼을 보았다. 정해는 단순했다. 정점 1에서 시작하여 각 정점의 최단 경로를 구하면 된다. 문제에서 정의하는 "N-1개의 간선으로 이루어진 트리에서 정점 1에서 출발하여 정점 i에 도달하기까지 걸리는 가중치의 합"을 $d_i$라고 하자. 추가로, 정점 1에서 정점 i까지 도달하는데 걸리는 최단거리를 $D_i$라고 하자. 그렇다면 결국에 $d_i$가 $D_i$가 되면 문제에서 요구하는 $d_i$들의 합이 최소가 된다.
위 그림 1에서 빨간색으로 이루어진 간선들의 경로, 즉 1-2-3-4가 정점 1에서 정점 4로 가는 최단거리 경로라고 하자. 이 때, 해당 경로를 버리지 않고 N-1개의 트리의 일부로 채택한다고 하자. 그렇게 된다면 $D_4 = d_4$가 됨은 자명하다. 그런데, 이때, 빨간색으로 이루어진 다른 간선들의 경로, 즉 1-2-3은 정점 3의 입장에서 보았을 때 과연 최적일까?
정답은 "그렇다"이다. 귀류법으로 간단히 증명할 수 있다. 만일 1-2-3 경로보다도 더 적은 거리로 이동할 수 있는 경로가 있다고 하자. 그렇게 된다면 우리는 굳이 1-2-3-4를 택할 이유가 없다. 왜냐하면 4의 입장에서는 최적이나, 3의 입장에서는 최적이 아니기 때문이다. 그런데, 만일 1-2-3-4 경로보다 더 작은 거리로 4까지 도달할 수 있었다면, 애초에 다익스트라 알고리즘이 해당 경로를 찾아주었을 것이다. 그런데 실제 다익스트라의 결과와 다르다면 결국 다익스트라 알고리즘이 틀린 알고리즘이거나, 1-2-3-4의 경로보다 더 작은 거리로 갈 수 없거나 둘 중 하나는 참이다. 그런데, 다익스트라 알고리즘에 대한 증명은 이미 수학적으로 완료되었기 때문에 전자가 참이 될 수 없으므로 후자, 즉 1-2-3-4의 경로보다 더 작은 거리로 갈 수 없다는 것이 참이다.
따라서, 정점 3의 입장에서도 정점 4로 도달하기 위해 거쳤던 경로를 택하는 것이 최적이다. 재귀적으로 정점 2 역시 마찬가지이다. 따라서, 특정 최단 경로에 속하는 정점들은 해당 경로를 이루는 간선을 선택하는 것이 최단거리와 같아진다.
import sys, heapq # go : 정점 1에서 다익스트라 알고리즘을 실행하였을 때 각 정점을 방문하기 전에 어떤 정점을 방문했는지 # 저장한 배열을 리턴하는 함수 def go(): # d : 최단거리를 저장하는 배열 d = [-1] * (n + 1) # path : 특정 인덱스(정점) i가 최단 경로를 따를 때 이전에 거친 정점 번호를 저장하는 배열 path = [-1] * (n + 1) # 다익스트라 실행 q = [] heapq.heappush(q, (0, 1)) while q: now_dist, now = heapq.heappop(q) if d[now] != -1 and d[now] < now_dist: continue for (next, next_dist) in adj[now]: if d[next] == -1 or d[next] > next_dist + now_dist: d[next] = next_dist + now_dist # 이전에 방문한 정점 갱신 path[next] = now heapq.heappush(q, (next_dist + now_dist, next)) return path n, m = map(int, sys.stdin.readline().split()) # adj : 인접 리스트 adj = [[] for _ in range(n + 1)] # cache : 특정 간선이 몇번째 간선인지 저장하는 dictionary cache = dict() for i in range(m): a, b, c = map(int, sys.stdin.readline().split()) cache[(a, b)] = i + 1 adj[a].append((b, c)) adj[b].append((a, c)) res = go() # 각 정점별 이전에 도달했던 정점과 이루는 간선의 간선 번호 출력 for i in range(2, n + 1): print(cache[(min(i, res[i]), max(i, res[i]))], end=' ')
후기 : 브라운에서 그린이 되었다! 내용도 나쁘지 않았지만, C번에서 너무 허무하게 페널티를 먹어서 아쉬웠다. d까지 풀었을 때 시간이 대략 55분 남짓 남았어서 기분이 좋았다. 하지만, E번도 충분히 풀 수 있었는데 스스로 뇌절을 많이 해서 아쉬운 문제였다. 남은 대회도 우선은 d번까지 빠르게 푸는 것을 목표로 해야겠다. 어제 참가한 nomura(Atcoder Beginner Contest 253)도 업솔빙한 후 여유가 될 때 포스팅해야 겠다
'AtCoder' 카테고리의 다른 글
Atcoder Beginner Contest 265 후기 (0) 2022.08.23 Atcoder Beginner Contest 253 후기 (0) 2022.06.18 AtCoder Beginner Contest 250 후기 (0) 2022.05.08 AtCoder Beginner Contest 249 후기 (0) 2022.05.01 Atcoder Beginner Contest 243 후기 (2) 2022.03.13