Compare commits

...

106 Commits

Author SHA1 Message Date
sm4640
8460030a1f 1504-g4 성공 2026-04-04 23:27:21 +09:00
sm4640
2d5535fe97 2293-g4 성공 2026-04-03 17:13:14 +09:00
sm4640
572fb9b1ce 14003-p5 성공 2026-04-03 16:55:10 +09:00
sm4640
f336fd8e5f 14002-g4 성공 2026-04-03 16:52:49 +09:00
sm4640
8e080abe52 2240-g4 성공 2026-04-02 20:57:49 +09:00
sm4640
32ec66e6a9 1654-s2 성공 2026-04-02 17:19:13 +09:00
fc2523e4ae 15486-g5 답보고 성공 2026-04-01 17:24:47 +09:00
fbbd5d3809 2302-g5 성공 2026-04-01 15:47:11 +09:00
sm4640
2b698b487e 12852-g5 성공 2026-03-31 13:36:50 +09:00
sm4640
8e68290fe3 11052-s1 성공 2026-03-30 17:41:13 +09:00
sm4640
ae7df30365 10844-s1 성공 2026-03-30 14:57:21 +09:00
sm4640
4e13009885 9465-s1 성공 2026-03-30 13:57:45 +09:00
sm4640
eded08a5d1 2156-s1 성공 2026-03-30 12:35:45 +09:00
sm4640
090f779d52 4883-s1 성공 2026-03-30 12:24:08 +09:00
sm4640
992da4d0f5 4883-s1 성공 2026-03-30 12:00:23 +09:00
sm4640
7429d960d2 11057-s1 성공 2026-03-30 11:14:49 +09:00
sm4640
7f235e6836 1932-s1 성공 2026-03-30 09:46:31 +09:00
sm4640
5d300435a5 1149-s1 성공 2026-03-29 00:52:06 +09:00
sm4640
f79594f871 1912-s2 성공 2026-03-28 01:42:09 +09:00
sm4640
a1c9fe6798 11053-s2 성공 2026-03-27 17:34:41 +09:00
sm4640
3222e78317 15988-s2 성공 2026-03-27 17:14:33 +09:00
sm4640
5d7eebc8f1 11055-s2 성공 2026-03-27 17:00:42 +09:00
sm4640
3a802d35c3 11726-s3 성공 2026-03-27 15:43:53 +09:00
sm4640
97cf2d9e54 2193-s3 성공 2026-03-27 15:26:04 +09:00
sm4640
30ee1143fb 11659-s3 성공 2026-03-27 12:27:47 +09:00
sm4640
a62fcf41a3 1904-s3 성공 2026-03-27 12:13:54 +09:00
sm4640
77f4339bbf 1463-s3 성공 2026-03-27 11:04:28 +09:00
sm4640
87e7ad2fcb 9461-s3 성공 2026-03-27 10:30:14 +09:00
sm4640
8f84a61a23 9095-s3 성공 2026-03-27 10:10:15 +09:00
sm4640
2dbd06f4f6 11727-s3 성공 2026-03-27 09:23:14 +09:00
sm4640
81fd3ef46e 1003-s3 성공 2026-03-26 23:02:47 +09:00
sm4640
f28354b79c 2579-s3 성공 2026-03-26 21:43:22 +09:00
sm4640
e84f83b0b7 14501-s3 성공 2026-03-26 17:54:29 +09:00
sm4640
159c3878a6 2748-b1 성공 2026-03-26 16:38:55 +09:00
sm4640
618f6dc8bf 2179-g3 성공 2026-03-25 18:32:45 +09:00
sm4640
ae9db766c4 1253-g4 성공 2026-03-25 14:56:31 +09:00
633494610a 4485-g4 성공 2026-03-23 10:14:26 +09:00
bd846871de 1976-g4 성공 2026-03-18 15:52:28 +09:00
409f293091 1987-g4 성공 2026-03-18 15:23:20 +09:00
b0ec14828b 9935-g4 성공 2026-03-18 14:00:10 +09:00
32616725c5 2631-g4 풀이 보고 품 2026-03-16 17:41:26 +09:00
e5f5c64f26 2631-g4 풀이 보고 품 2026-03-16 17:41:09 +09:00
6ad8ca72e6 1806-g4 성공 2026-03-16 13:58:01 +09:00
fc52790b41 2467-g5 성공 2026-03-12 15:59:26 +09:00
5b3ecdc596 2467-g5 성공 2026-03-12 12:38:45 +09:00
e35a7cbbaf 5972-g5 성공 2026-03-11 12:26:46 +09:00
4118f8f7a0 5972-g5 성공 2026-03-11 12:26:35 +09:00
7c30191f63 20055-g5 성공 2026-03-11 10:40:37 +09:00
93ea0d98a9 22251-g5 성공 2026-03-09 11:04:21 +09:00
4d7b40c0fd 2493-g5 성공 2026-03-09 09:42:10 +09:00
45d79b7bd0 13549-g5 성공 2026-03-04 15:29:24 +09:00
d0d8869b5c 20437-g5 성공 2026-03-03 15:46:31 +09:00
633ec60c73 12919-g5 성공 2026-02-27 10:42:26 +09:00
edb54cb0a4 16928-g5 성공 2026-02-26 15:41:17 +09:00
2e1b508e92 15989-g5 성공 2026-02-25 14:04:58 +09:00
7bcb4dfdc4 2668-g5 성공 2026-02-25 11:16:26 +09:00
3777344b56 14719-g5 성공 2026-02-23 16:33:43 +09:00
9dafd92505 7490-g5 풀이 보고 성공 2026-02-23 14:16:59 +09:00
c902220c1d 7490-g5 풀이 보고 성공 2026-02-23 14:10:12 +09:00
0e8e68d12f 폴더 분리 2026-02-23 11:14:50 +09:00
4aebd20cf5 1697-s1 성공 2026-02-23 10:55:37 +09:00
ecb877621f 2531-s1 성공 2026-02-19 17:16:41 +09:00
630d1141c1 1522-s1 풀이 확인 후 성공 2026-02-19 14:53:23 +09:00
88ce2a45b1 1283-s1 성공 2026-02-16 20:52:36 +09:00
54307abd2a 17615-s1 성공 2026-02-16 18:41:21 +09:00
f6df1e7883 1446-s1 성공 2026-02-15 06:17:55 +09:00
3c5ddbd8dc 20922-s1 성공 2026-02-14 18:03:45 +09:00
1da1eeb10c 14940-s1 성공 2026-02-13 16:41:22 +09:00
21c4366e58 14940-s1 성공 2026-02-13 16:40:57 +09:00
524b9d7b4a 1260-s2 성공 2026-02-12 13:50:08 +09:00
b5fc24cbce 1406-s2 성공 2026-02-12 11:06:17 +09:00
28a212e4e7 3758-s2 성공 2026-02-11 13:55:09 +09:00
e1e7033acc 2607-s2 성공 2026-02-10 14:08:08 +09:00
3ca18fe457 11501-s2 하는중 2026-02-09 20:01:03 +09:00
5d4a3bd574 2304-s2 성공 2026-02-09 18:55:58 +09:00
cd9ef4df24 2304-s2 성공 2026-02-09 18:28:53 +09:00
20a21cd6b0 1515-s2 성공 2026-02-08 15:57:35 +09:00
867aab2d5b 1138-s2 성공 2026-02-05 19:26:20 +09:00
b46632a93b 1138-s2 성공 2026-02-05 19:12:14 +09:00
7509610c77 2512-s2 성공 2026-02-05 14:46:15 +09:00
7f7c0475eb 20006-s2 성공 2026-02-05 09:35:54 +09:00
158d5b7f23 1927-s2 성공 2026-02-04 17:24:42 +09:00
279e6eb00f 19637-s3 성공 2026-02-04 17:20:27 +09:00
dddc4668be 19637-s3 성공 2026-02-04 17:17:47 +09:00
c786856f03 21921-s3 성공 2026-02-03 13:27:25 +09:00
586c1d7130 17484-s3 성공 2026-02-03 12:27:24 +09:00
10ac4d6d2e 20920-s3 성공 2026-02-03 10:12:42 +09:00
13f1bcff12 19941-s3 성공 2026-02-02 14:53:35 +09:00
f0cd496882 13305-s3 성공 2026-01-30 13:24:47 +09:00
f8d5ba62b1 20310-s3 성공 2026-01-30 12:17:56 +09:00
b287d194af 22233-s3 성공 2026-01-29 23:44:46 +09:00
1129cfd5d3 2075-s3 성공 2026-01-29 15:24:16 +09:00
2f4757f560 9017-s3 성공 2026-01-28 15:53:58 +09:00
7bba77f228 17266-s4 성공 2026-01-28 14:13:58 +09:00
c679c7f481 1205-s4 성공 & 이분탐색 총정리 2026-01-27 15:19:05 +09:00
70a785013a 20125-s4.py 성공 2026-01-27 00:40:19 +09:00
472bac7e4e 2164-s4 성공 + 코멘트 추가 2026-01-26 13:57:13 +09:00
35eecf2294 2164-s4 성공 2026-01-26 13:49:46 +09:00
66e9cc1b83 1244-s4 성공 2026-01-26 13:28:19 +09:00
0d13cbf915 9655-s5 성공 + dp 식 추론 2026-01-23 17:40:42 +09:00
4eb1c87b5e 9655-s5 성공 2026-01-23 13:48:56 +09:00
956e00feb1 4659-s5 성공 2026-01-22 13:57:18 +09:00
sm4640
9bdbaec471 25757-s5 성공 2026-01-21 20:43:13 +09:00
sm4640
944995535d 문제 해결 템플릿 2026-01-21 18:46:38 +09:00
sm4640
a8b8451e08 Merge branch 'main' of https://nkeystudy.site/gitea/nkey/baekjoon-study 2026-01-21 14:32:35 +09:00
sm4640
dbb22d9092 10431-s5.py 성공 2026-01-21 14:32:00 +09:00
99 changed files with 4729 additions and 0 deletions

51
14003-p5.py Normal file
View File

@@ -0,0 +1,51 @@
# 가장 긴 증가하는 부분 수열 5
import sys
from bisect import bisect_left
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = list(map(int, input().rstrip().split()))
parent = [-1] * (n)
result = [lst[0]]
result_idx = [0]
for i in range(1, n):
if lst[i] > result[-1]:
parent[i] = result_idx[-1]
result.append(lst[i])
result_idx.append(i)
continue
idx = bisect_left(result, lst[i])
if result[idx] != lst[i]:
result[idx] = lst[i]
result_idx[idx] = i
if idx != 0:
parent[i] = result_idx[idx-1]
answer = [result[-1]]
parent_idx = parent[result_idx[-1]]
while parent_idx != -1:
answer.append(lst[parent_idx])
parent_idx = parent[parent_idx]
print(len(answer))
print(*answer[::-1])
return
solution()
"""
걸린 시간: 2분 ㅋㅋ
시간 복잡도: 14002와 같게 모든 수에 대해. 이분 탐색을 진행하기 때문에 O(nlogn)이다.
해설: 14002번(가장 긴 증가하는 부분 수열 4) 답을 그대로 가져와서 binary_search를 직접 구현하지 않고, bisect를 활용해서 구현하였다.
내가 들어갈 자리는 나보다 처음으로 큰 녀석의 자리이기 때문에 사실상 왼쪽으로 들어간다고 생가하면 된다.(오른쪽은 제거하지만..)
따라서 bisect_left를 활용하였다.
또한 bisect_left에는 리스트 원소들이 tuple이면 안되기 때문에, result_idx라는 리스트를 따로 만들어서 진행하였다.
"""

64
1504-g4.py Normal file
View File

@@ -0,0 +1,64 @@
# 특정한 최단 경로
import sys
import heapq
input = sys.stdin.readline
def get_short(start, g, n):
dp = [float('inf')] * (n+1)
pq = [(0, start)]
dp[start] = 0
while pq:
dist, now = heapq.heappop(pq)
if dist > dp[now]:
continue
for nxt, v in g[now]:
nxt_dist = dist + v
if nxt_dist < dp[nxt]:
dp[nxt] = nxt_dist
heapq.heappush(pq, (nxt_dist, nxt))
return dp
def solution():
n, e = map(int, input().rstrip().split())
g = {i: [] for i in range(1, n+1)}
for i in range(e):
a, b, c = map(int, input().rstrip().split())
g[a].append((b,c))
g[b].append((a,c))
v1, v2 = map(int, input().rstrip().split())
start_1 = get_short(1, g, n)
start_v1 = get_short(v1, g, n)
start_v2 = get_short(v2, g, n)
result = min(start_1[v1] + start_v1[v2] + start_v2[n], start_1[v2] + start_v2[v1] + start_v1[n])
if result == float('inf'):
result = -1
print(result)
return
solution()
"""
걸린 시간: 35분
시간 복잡도: 간선에 연결된 각 정점에 대해 모든 연결된 선을 확인하기 때문에 그냥 간선의 길이만큼 본다고 생각하면 된다.
이때, 간선 하나를 보고 heap 조작을 한번 하기 때문이 loge이다.
따라서 전체 시간복잡도는 O(eloge)이다.
해설: 1 -> v1 -> v2 -> n, 1 -> v2 -> v1 -> n 중 더 짧은 것을 구하면 된다.
1, v1, v2 기준으로 다 구하면 된다.
다익스트라는 하던대로 구현하면 완료.
"""

47
1654-s2.py Normal file
View File

@@ -0,0 +1,47 @@
# 랜선 자르기
import sys
input = sys.stdin.readline
def binary_search(l, r, lan_lst, target):
result = 0
while l <= r:
count = 0
mid = (l+r)//2
for lan in lan_lst:
count += (lan // mid)
if count >= target:
l = mid+1
result = mid
else:
r = mid-1
return result
def solution():
k, n = map(int, input().rstrip().split())
max_len = 0
lan_lst = [0] * k
for i in range(k):
lan_lst[i] = int(input().rstrip())
max_len = max(max_len, lan_lst[i])
result = binary_search(l=1, r=max_len+1, lan_lst=lan_lst, target=n)
print(result)
return
solution()
"""
걸린 시간: 1시간
시간 복잡도: 1부터 가장 큰 랜선 길이까지의 범위에서 이분탐색을 진행하므로 O(log(2^31-1))이고, 이분탐색을 할 때마다 k개의 랜선을 잘라보기 때문에 O(klogn)이다.
해설: 최대 길이를 찾는 것이기 때문에 주어진 랜선 중 가장 긴 길이부터 아래로 내려가면서 n개가 나오는 첫 지점을 찾으면 된다.
근데 이렇게 하면 랜선의 길이가 최대 2^31-1이므로 너무 오래 걸린다.
마침 수직선이기 때문에 이분탐색을 하면 되겠다는 생각을 했고, 이렇게 최적화 문제를 결정문제로 바꿔서 푸는 것을 파라메트릭 서치라고 한다.
이분 탐색 구현은 count가 n보다 크면 일단 답 후보로 넣어놓고, l을 mid+1로 옮겼고, 반대의 상황일 때는 답 후보로 넣지 않고, r을 mid-1로 했다.
"""

21
template.py Normal file
View File

@@ -0,0 +1,21 @@
# 제목
import sys
input = sys.stdin.readline
def solution():
return
solution()
"""
걸린 시간:
시간 복잡도:
해설:
"""

View File

@@ -0,0 +1,30 @@
# 피보나치 수 2
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dp = [0] * 100
dp[0] = 0
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
print(dp[n])
return
solution()
"""
걸린 시간: 3분
시간 복잡도: n까지 dp 테이블을 채우면 되므로 O(n)이다.
해설: i번째 칸을 채우기 위해서는 i-1, i-2번째 값을 알아야하기 때문에 dp테이블에 이전 값들을 기록해가면서 현재 값을 채우면 된다.
"""

View File

@@ -0,0 +1,54 @@
# 1로 만들기 2
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dp = [float('inf')]*(n+1)
dp[n] = (0, n)
ways = [3, 2]
for i in range(n-1, 0, -1):
min_count = float('inf')
min_from = -1
for way in ways:
before = i * way
if before > n:
continue
if min_count > dp[before][0]:
min_count = dp[before][0]
min_from = before
before = i + 1
if before > n:
continue
if min_count > dp[before][0]:
min_count = dp[before][0]
min_from = before
dp[i] = (min_count+1, min_from)
result = [1]
idx = 1
while idx < n:
idx = dp[idx][1]
result.append(idx)
print(dp[1][0])
print(*result[::-1])
return
solution()
"""
걸린 시간: 25분
시간 복잡도: dp 테이블 채우고 역추적까지 O(n)이다.
해설: 1463(1로 만들기 문제)번에서 과정을 출력하는 과정이 추가되었다. 이는 dp 테이블을 채울 때, 어디서 왔는지만 기록해주면 마지막에 역추적을 하면 된다.
"""

View File

@@ -0,0 +1,64 @@
# 가장 긴 증가하는 부분 수열 4
import sys
input = sys.stdin.readline
def binary_search(result, target):
l, r = 0, len(result)-1
idx = len(result)
while l <= r:
mid = (l+r)//2
if result[mid][0] >= target:
idx = mid
r = mid - 1
else:
l = mid + 1
return idx
def solution():
n = int(input().rstrip())
lst = list(map(int, input().rstrip().split()))
parent = [-1] * (n)
result = [(lst[0], 0)]
for i in range(1, n):
if lst[i] > result[-1][0]:
parent[i] = result[-1][1]
result.append((lst[i], i))
continue
idx = binary_search(result, lst[i])
if result[idx][0] != lst[i]:
result[idx] = (lst[i], i)
if idx != 0:
parent[i] = result[idx-1][1]
answer = [result[-1][0]]
parent_idx = parent[result[-1][1]]
while parent_idx != -1:
answer.append(lst[parent_idx])
parent_idx = parent[parent_idx]
print(len(answer))
print(*answer[::-1])
return
solution()
"""
걸린 시간: 39분
시간 복잡도: 하나의 수에 대해. 이진탐색을 진행하기 때문에 O(nlogn)이다.
해설: 가장 긴 증가하는 부분 수열 문제에서 수열 자체를 알아야 하는 문제이다.
기존에 하던 방식은 개수만 원했기 때문에 결과 리스트에 업데이트 하면서 진행했다.
하지만 결과 리스트에 있는 것들은 그것 자체가 수열이 아니고, result[i]는 i개의 증가하는 수열들의 맨 뒤 값 중 가장 작은 값이다.
따라서 result를 업데이트 하면서 현재 보는 수의 바로 이전 수를 기록해놔야 한다.(parent 기록)
바로 이전 수는 result에서 binary_search를 했을 때, 본인이 들어갈 idx의 앞에 있는 수가 내가 맨 뒤일때 최선의 수이다.
따라서 그 수의 idx를 계속 기록하면 된다.
마지막에 result[-1]의 parent를 계속 역추적 하면 하나의 수열이 완성된다.
"""

View File

@@ -0,0 +1,46 @@
# 퇴사 2
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
t = [0] * (n+1)
p = [0] * (n+1)
for i in range(1, n+1):
t[i], p[i] = map(int, input().rstrip().split())
dp = [0] * (n+2)
max_p = 0
for i in range(1, n+1):
max_p = max(max_p, dp[i])
nxt = i+t[i]
if nxt <= n+1:
dp[nxt] = max(dp[nxt], max_p+p[i])
print(max(max_p, dp[n+1]))
return
solution()
"""
걸린 시간: 몰라
시간 복잡도: 과거에서 미래 한 시점에만 업데이트를 하기 때문에 n 테이블을 다 채우기만 하면 되므로 O(n)이다.
해설: 14501(퇴사)번 문제는 n이 작았기 때문에 dp[i]를 채울 때 이전 값들 중에 i로 올 수 있는 것들을 전부 보고 최대값을 구하는
O(n^2)로 해도 괜찮았다.
근데 이번 문제는 n이 많이 크기 때문에 기존 방식은 안된다.
따라서 미래에서 과거를 보는 것이 아닌 과거에서 미래로 값을 놓는 방식을 선택했다.(답 보고 알았음)
i를 볼 때 지금까지의 최대 수익을 계속 기록해놓으면서 i에서 상담을 했다고 쳤을 때,
상담이 끝나는 날(nxt)에 i에 상담을 한 경우(max_p + p[i])와 더 과거에서 온 이력(dp[nxt])중 최대를 비교해서 갱신한다.
이렇게 기록해놓으면 미래에는 과거에서 이 미래까지 온 최선의 경우의 수를 이미 가지고 있고,
이것과 별개로 여기서 상담을 하지 않았을 때 최대 수익도 가지고 있다.
이 둘을 계속 비교해가며 최대 수익을 갱신하고, 마지막에는 n+1날까지 온 최선의 수와 전체 최대 수익을 비교하여 마무리 한다.
max_p = max(max_p, dp[n+1])은 하지 않았었기 때문.
"""

View File

@@ -0,0 +1,48 @@
# 자두나무
import sys
input = sys.stdin.readline
def solution():
t, w = map(int, input().rstrip().split())
plum = [0] * (t+1)
for i in range(1, t+1):
plum[i] = int(input().rstrip())
dp = [[0] * (t+1) for _ in range(w+1)]
for i in range(1, t+1):
dp[0][i] = dp[0][i-1]
if plum[i] == 1:
dp[0][i] += 1
for i in range(1, w+1):
for j in range(1, t+1):
dp[i][j] = max(dp[i][j-1], dp[i-1][j-1])
if (plum[j] == 1 and i%2 == 0) or (plum[j] == 2 and i%2 == 1):
dp[i][j] += 1
result = 0
for i in range(w+1):
result = max(result, dp[i][t])
print(result)
return
solution()
"""
걸린 시간: 40분
시간 복잡도: w*t의 테이블을 채워야 하기 때문에 O(w*t)이다.
해설: 현재의 상황은 몇번째 이동한 상황인지로 나뉘고, w만큼 이동했을 때 t초의 상황은 이번에 움직였거나, 이번에 안 움직였거나이다.
따라서 dp[w][t] = max(dp[w][t-1], dp[w-1][t-1]) 이다.
이때, w가 홀수면 2번, 짝수면 1번 나무에 있는 것이므로 t초에 떨어질 나무와 w의 홀짝 여부가 맞으면 +1을 해줘야한다.
그리고 t초의 최대값은 이동을 얼마나 했을 때인지 모르기 때문에 마지막에 t초의 전체 w에 대해 최대값을 구해야한다.
"""

View File

@@ -0,0 +1,32 @@
# 동전 1
import sys
input = sys.stdin.readline
def solution():
n, k = map(int, input().rstrip().split())
dp = [0] * (k+1)
dp[0] = 1
for _ in range(n):
coin = int(input().rstrip())
for i in range(coin, k+1):
dp[i] = dp[i-coin] + dp[i]
print(dp[k])
return
solution()
"""
걸린 시간: 13분
시간 복잡도: 동전 개수(n)만큼 k 길이의 테이블을 업데이트 해야하기 때문에 O(n*k)이다.
해설: 조합 조건으로 여러 동전을 활용해서 k 값을 만들어야 한다.
이는 순서를 정해줘야 하기 때문에 동전을 하나씩 쓰면서 전체 dp를 순회해야 한다.
1, 2, 5의 동전이 있다면 1 썼을 때 전체 dp, 2도 썼을 때 전체 dp 이런식으로.. (동전 사용 순서는 상관없다. 조합이니까 하나로만 정해지면 됨)
"""

View File

@@ -0,0 +1,49 @@
# 극장 좌석
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
m = int(input().rstrip())
vip = [0] * (n+1)
for _ in range(m):
vip[int(input().rstrip())] = 1
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
if vip[i] or vip[i-1]:
dp[i] = dp[i-1]
else:
dp[i] = dp[i-1] + dp[i-2]
print(dp[n])
return
solution()
"""
걸린 시간: 16분
시간 복잡도: n개의 dp 테이블을 채우면 되기 때문에 O(n)이다.
해설: dp[i]를 i번 좌석까지 왔을 때 경우의 수라고 하자.
i에 같은 번호가 잘 앉거나, 옆으로 이동하거나 2가지의 경우가 있다.
i가 잘 앉은 경우에는 이전에서 변동이 없기 때문에 dp[i-1]을 하면된다.
i가 옆으로 이동한 경우에는 왼쪽과 오른쪽 이동이 있는데, dp 테이블을 오른쪽으로 채워가기 때문에 오른쪽으로 이동하는 것은 지금 세지말자.
그러면 i는 i-1자리로 이동할 수 있다. 그럼 i-1에 있는 애는 어디로 갈까? 반드시 i와 스왑을 해야한다.
왜 why? 한쪽으로 계속 미는 방식으로 앉으면 마지막 사람은 앉을 곳이 없기 때문에 이 문제는 무조건 스왑이다.
그렇기 때문에 오른쪽 이동은 dp[i+1]에서 왼쪽으로 스왑할때 책임져 줄 것이다.
아무튼 i, i-1 자리가 바뀌었기 때문에 dp[i-2] 상황에서 새로운 경우가 발생한 것이다.
따라서 dp[i] = dp[i-1] + dp[i-2]이다.
이때 주의할 점은 vip인데, vip는 이동할 수 없고 무조건 본인 자리에 앉아야 하기 때문에 dp[i] = dp[i-1]이 된다.
또한 우리는 왼쪽으로의 이동만 보기로 했기 때문에 vip의 오른쪽에 있는 사람도 왼쪽으로 이동을 할 수 없다.
따라서 그 사람도 dp[i] = dp[i-1]이다.
"""

View File

@@ -0,0 +1,33 @@
# 피보나치 함수
import sys
input = sys.stdin.readline
def solution():
t = int(input().rstrip())
dp = [0] * 50
dp[0] = (1, 0)
dp[1] = (0, 1)
for i in range(2, 50):
dp[i] = (dp[i-1][0]+dp[i-2][0], dp[i-1][1]+dp[i-2][1])
for _ in range(t):
k = int(input().rstrip())
print(*dp[k])
return
solution()
"""
걸린 시간: 15분
시간 복잡도: n은 40보다 작다고 했기 때문에 dp테이블을 대략 50까지만 채우면 되므로 O(n)이다.
해설: 현재 보는 수는 결국 1개 전과, 2개 전에 사용했던 0, 1의 개수의 합이므로 dp 테이블을 만들어서 계속 채워나가면 된다.
"""

View File

@@ -0,0 +1,44 @@
# 쉬운 계단 수
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
MOD = 1000000000
dp = [[1]*10 for _ in range(n)]
dp[0][0] = 0
for i in range(1, n):
for j in range(10):
if i == 1 and j == 1:
dp[i][j] = dp[i-1][j+1]
continue
if j == 0:
dp[i][j] = dp[i-1][j+1]
elif j == 9:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = (dp[i-1][j-1] + dp[i-1][j+1])%MOD
print(sum(dp[n-1])%MOD)
return
solution()
"""
걸린 시간: 25분
시간 복잡도: n*10 칸을 채워야하기 때문에 O(10n)이다.
해설: dp[i][j]는 i번째 자리수가 j로 끝날때 경우의 수이다.
이전 행의 앞뒤에서만 올 수 있으므로, dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]이다.
0과 9는 각각 오른쪽과 왼쪽에서만 올 수 있고, 두번째 행에서 1은 0으로 시작할 수 없기 때문에 오른쪽에서만 와야한다.
위 조건 그대로 코드로 구현한 뒤 mod로 나머지들만 계속 업데이트해가면 된다.
마지막 수는 어떤 숫자든 가능하기 때문에 마지막 행의 모든 값을 더한 후 mod해서 답을 구한다.
mod 공식은 1904 풀이를 확인하자.
"""

View File

@@ -0,0 +1,33 @@
# 카드 구매하기
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = [0] + list(map(int, input().rstrip().split()))
dp = lst[:]
dp[1] = lst[1]
for i in range(2, n+1):
for j in range(i-1, 0, -1):
dp[i] = max(dp[i], dp[j]+lst[i-j])
print(dp[n])
return
solution()
"""
걸린 시간: 35분
시간 복잡도: O(n^2)이다.
해설: dp[i]는 카드 i개를 모으고자 할때, 최대값이라고 하자.
i개를 모으기 위해 i개보다 작은 수들에서 i로 올 수 있는 모든 경우의 수를 보고 최대값을 선택한다.
"""

View File

@@ -0,0 +1,54 @@
# 가잔 긴 증가하는 부분 수열
import sys
input = sys.stdin.readline
def binary_search(target, lis):
l, r = 0, len(lis)
while l < r:
mid = (l+r)//2
if lis[mid] >= target:
r = mid
else:
l = mid + 1
return l
def solution():
n = int(input().rstrip())
lst = list(map(int, input().rstrip().split()))
lis = []
for k in lst:
if not lis or k > lis[-1]:
lis.append(k)
continue
lis[binary_search(k, lis)] = k
print(len(lis))
return
solution()
"""
걸린 시간: 10분
시간 복잡도: 내 풀이는 모든 수를 보는데 그 수가 lis에서 들어갈 자리를 이분탐색으로 확인하기 때문에 O(nlogn)이다.
해설: 원래는 11055(가장 큰 증가하는 부분수열)번처럼 하나의 수를 볼때 앞에 나온 수중에 본인보다 작은 수의 dp 값을 보고 거기에 +1한 값의 최대가
본인을 포함한 증가하는 수열의 길이이다. 근데 이것도 O(n^2)이다.
따라서 나는 길이만 알면 되기 때문에 좀 더 시간을 줄여보았다.
각 숫자를 순서대로 보면서 lis라는 리스트에 최대한 작은 수들을 넣는 것이다.
예를 들어, [1, 5, 8] 이렇게 있을 때, 지금 보는 수가 3이라면 5대신 3으로 바꿔끼면 된다.
이렇게 되면 3이 나중에 나온건데, 8이 아직도 있으니까 헷갈릴 수 있다.
하지만 이건 lis[i]를 i개의 길이에서 가장 작은 녀석이라고 생각하면 된다.
그럼 lis[2]는 2개짜리 수열 중에 제일 큰 놈의 최소가 3으로 됐을 뿐이고, lis[3]은 3개짜리 수열 중에 제일 큰 놈의 최소가 8일 뿐이다.
이렇게 진행을 하고 lis[-1]보다 큰 놈이 들어오면 그냥 뒤에 붙이면 된다.
이렇게 하면 원래 수열은 모르지만(복원하려면 따로 기록해야 한다.) 최대 길이를 알 수 있다.
"""

View File

@@ -0,0 +1,29 @@
# 가장 큰 증가하는 부분 수열
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = list(map(int, input().rstrip().split()))
dp = lst[:]
for i in range(1, n):
dp[i] = max([dp[j]+lst[i] for j in range(i-1, -1, -1) if lst[j] < lst[i]] + [lst[i]])
print(max(dp))
return
solution()
"""
걸린 시간: 25분
시간 복잡도: 하나의 값을 볼때 내 앞에 있는 값들을 다 확인해봐야하기 때문에 O(n^2)이다.
해설: 현재 보는 값보다 앞에 나온 작은 놈들의 dp 값에 나를 더하면, 이는 앞에 나온 수열 중 내가 붙을 수 있는 것들 중에 값이 최대인 것에 붙겠다라는 뜻이다.
"""

View File

@@ -0,0 +1,43 @@
# 오르막 수
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dp = [1]*10
for _ in range(1, n):
temp = dp[-1]
dp[-1] = sum(dp)
for i in range(8, -1, -1):
temp2 = dp[i]
dp[i] = dp[i+1] - temp
temp = temp2
print(sum(dp)%10007)
return
solution()
"""
걸린 시간: 50분
시간 복잡도: 10자리의 dp 테이블을 n번만큼 반복하며 업데이트하기 때문에 O(10n)이다.
해설: 한 자리수 일때는 0~9까지 그냥 10개이다.
두 자리수 일때는 0일때 0~9, 1일때 1~9 ... 로 10개부터 1개까지 다 더한 55개이다.
세 자리수 일때는 0--일때, 55개, 1--일때, 11부터 99까지이므로 1~9 ... 1~1까지 45개이므로, 두 자리수 기준으로 0~9일때, 1~9일때.. 경우의 수를 가져온다.
그렇다는것은 자리수가 늘어날수록 10개의 수 기준으로 합이 정답이 된다는 뜻이다.
따라서 dp = [1]*10으로 해놓고, 자리수가 늘어날수록 합을 계속 계산하면 된다.
나는 논리 그대로 55에서 10을 빼고, 45에서 9를 빼고.. 순서로 가다보니 dp를 뒤에서부터 채우느라 temp가 2개 등장했지만,
사실 이게 앞에서부터 누적합을 계산하는 것이랑 똑같다.
수식으로 이해해보자면, dp[i][j]를 i자리수의 끝 숫자가 j일때 경우의 수로 생각해보면, dp[i][j] = dp[i-1][0] + dp[i-1][1] + .. + dp[i-1][j]이다.
이건 결국 전 행의 누적합을 의미한다. 또한 dp[i][j-1] = dp[i][0] + .. + dp[i][j-1]이므로, dp[i][j] = dp[i][j-1] + dp[i-1][j]가 된다.
따라서 1차원으로 만들면 dp[i] = dp[i-1] + dp[i](이전)이 된다.
"""

View File

@@ -0,0 +1,39 @@
# RGB 거리
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = [list(map(int, input().rstrip().split())) for _ in range(n)]
dp = [[float('inf') for _ in range(3)] for _ in range(n)]
dp[0] = lst[0]
for i in range(1, n):
for j in range(3):
dp[i][j] = min([dp[i-1][k]+lst[i][j] for k in range(3) if k != j])
print(min([dp[n-1][j] for j in range(3)]))
return
solution()
"""
걸린 시간: 20분
시간 복잡도: n*3의 dp 테이블을 채워야 하기 때문에 O(n)이다.
해설: 트리 형식으로 완전탐색을 해야할 것 같지만, 최선의 플레이만 잘 저장해놓으면 많이 배제시키고 할 수 있다.
bfs, 완전탐색, 백트래킹 같은 것이 떠오를 때, dp를 생각해보자.
현재 줄에서 3가지 중 하나를 볼때, 본인을 제외한 2가지 경우에 대해서 최선이 보장되어 있다면 지금 선택하는 색깔에 대해 최선의 과거를 가져온 것이다.
물론 지금의 선택이 최종 최선을 보장하진 않기 때문에 모든 색깔에 대해 모든 줄에서 최선의 선택을 하고, 마지막에는 그 최선들 중에 최선을 고르면 된다.
그럼 그리디 아니냐 할 수 있는데, 그리디로 커버할 수 없는 경우까지 커버하는 것이 이 풀이이다.
그리디는 계속 그 줄에서 최소를 선택하는 것이지만, 현재 보이는 최소만 선택했을 때 바로 다음에 같은 색깔로 굉장히 작은 숫자가 나오면 최선이 아니게 된다.
따라서 각 줄에서 3가지 중 뭘 선택했을 때 최선인지 모르기 때문에 3가지 색깔에 대해 그 전에 왔던 최선의 길을 계속 보장하는 것이다.
"""

View File

@@ -0,0 +1,35 @@
# 구간 합 구하기4
import sys
input = sys.stdin.readline
def input_ints():
return map(int, input().rstrip().split())
def solution():
n, m = input_ints()
lst = [0]+list(input_ints())
prefix_sum = lst[:]
for i in range(1, n+1):
prefix_sum[i] = prefix_sum[i-1]+lst[i]
for _ in range(m):
i, j = input_ints()
print(prefix_sum[j]-prefix_sum[i-1])
return
solution()
"""
걸린 시간: 9분
시간 복잡도: 누적합 리스트를 채우는데 O(n)이 걸린다.
해설: 구간 합을 m번 계산해야하는데, m이 100,000이하이므로 썼던 것을 재활용하지 않으면 시간초과가 날 것 같았다.
구간합은 누적합 - 누적합으로 계산할 수 있기 때문에 누적합을 처음에 계산해놓고 진행했다.
"""

View File

@@ -0,0 +1,33 @@
# 2xn 타일링
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
MOD = 10007
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = (dp[i-1] + dp[i-2])%MOD
print(dp[n])
return
solution()
"""
걸린 시간: 6분
시간 복잡도: dp 테이블 n개 채우면 O(n)이다.
해설: 현재 보는 칸이 생겼을 때 1x2를 넣으려면 i-1칸까지 차있는 상태에서 넣으면 되고, 2x1을 넣으려면 i-2칸까지 차있는 상태에서 넣어야 한다.
따라서 dp[i-1] + dp[i-2]이다.
"""

View File

@@ -0,0 +1,32 @@
# 2×n 타일링 2
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = (dp[i-1]*1) + (dp[i-2]*2)
print(dp[n]%10007)
return
solution()
"""
걸린 시간: 10분
시간 복잡도: n짜리 테이블을 한번씩만 보면서 채우기 때문에 O(n)이다.
해설: 과거부터 쌓아온 경우의 수가 현재의 결과에 영향을 미치고, 한 번 결정되면 바뀌지 않기 때문에 dp이다.
현재 1칸이 생겼을 때 과거로부터 놓는 경우를 생각해보면 꽉채워진 상태에서 2x1을 놓는 방법 1가지와 한칸 남겨놓고 2x2에서 만들 수 있는 경우의 수 2가지이다.
이전까지의 경우의 수에 다음 놓을 수 있는 경우의 수가 1가지라면 dp[i-1]*1이고, 2가지일때는 dp[i-2]*2이다.
이 둘을 합친 것이 현재 칸의 경우의 수이다.
"""

View File

@@ -0,0 +1,40 @@
# 퇴사
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
t_p = [(0, 0)] + [tuple(map(int, input().rstrip().split())) for _ in range(n)] + [(0, 0)]
dp = [0] * (n+2)
for i in range(1, n+2):
dp[i] = t_p[i][1]
for j in range(i-1, 0, -1):
if j+t_p[j][0] > i:
continue
dp[i] = max(dp[i], dp[j]+t_p[i][1])
print(dp[n+1])
return
solution()
"""
걸린 시간: 29분
시간 복잡도: 현재 보는 날짜에서 이전 날짜까지를 다 확인해보기 때문에 O(n^2)이다.
해설: 현재 보는 날짜로 올 수 있는 이전의 날짜들만 확인하고 싶었는데, set이나 dict에 계속 저장해놓고 갱신하는 것도 시간이 들기 때문에
그냥 이전 날짜를 다 보고 현재 날짜로 올 수 있는 날짜들만 계산을 했다.
근데 이걸 dp를 거꾸로 채우면 O(n)으로 가능하다.
현재 보는 날짜에 상담 여부를 결정하는데, 전제 조건은 현재 날짜에서 상담을 진행하면 퇴사날짜를 넘기지 않는 것이 조건이다.
만약 넘긴다면 그냥 dp[i+1]의 값을 가져와서 오늘은 상담을 안한 것으로 간주한다.
만약 퇴사 날짜를 넘기지 않는다면, max(dp[i+1], p[i]+dp[i+t[i]])로
오늘 상담을 했을 경우 끝나는 미래 시점의 값에 오늘 상담 값을 더한 것과 그냥 오늘 상담을 안했을 때 값을 비교하여 최대값을 계산한다.
"""

View File

@@ -0,0 +1,34 @@
# 1로 만들기
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dp = [float('inf')] * (n+1)
dp[n] = 0
for i in range(n-1, 0, -1):
way1 = dp[i*3] if i*3 <= n else float('inf')
way2 = dp[i*2] if i*2 <= n else float('inf')
way3 = dp[i+1] if i+1 <= n else float('inf')
dp[i] = min(way1+1, way2+1, way3+1)
print(dp[1])
return
solution()
"""
걸린 시간: 8분
시간 복잡도: n개의 dp 테이블을 채워야하기 때문에 O(n)이다.
해설: 현재 보는 수까지 오는 방법을 생각해봤을 때 3가지 방법이 있고, 거기서 최소 값에 +1을 한 것이 현재 보는 수까지 온 최단 경로이다.
범위 조건만 잘 설정해서 진행하면 끝
"""

View File

@@ -0,0 +1,37 @@
# 1, 2, 3 더하기 3
import sys
input = sys.stdin.readline
def solution():
t = int(input().rstrip())
MOD = 1000000009
dp = [0]*1000001
dp[0] = 1
dp[1] = 1
dp[2] = 2
for i in range(3, 1000001):
dp[i] = (dp[i-3] + dp[i-2] + dp[i-1])%MOD
for _ in range(t):
n = int(input().rstrip())
print(dp[n])
return
solution()
"""
걸린 시간: 6분
시간 복잡도: dp테이블 n개 다 채우는데 O(n)걸림. 그리고 테스트 케이스에 따라 더 걸리기 때문에 O(n+t)이다.
해설: 순서가 자유로운 숫자 만들기는 하나씩만 쓸 필요 없이, 여러 방향에서 올 수 있는 경우의 수를 다 고려해서 더하면 된다.
이 문제에서 포인트는 숫자가 너무 커서 mod를 써서 dp에 계속 저장해놔야 한다는 것이다.
mod 수식의 항등성은 1904번 문제 해설에서 확인하자.
"""

View File

@@ -0,0 +1,41 @@
# 01타일
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
mod = 15746
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = (dp[i-2]+dp[i-1])%mod
print(dp[n])
return
solution()
"""
걸린 시간: 19분
시간 복잡도: dp 테이블 채우기는 O(n)이다.
해설: 현재 보는 칸이 생겼을 때 과거의 어느 시점들에서 올 수 있는지 확인해야 한다. -1번째 칸에서 1 타일만 붙인 경우, -2번째 칸에서 00 타일만 붙인 경우이다.
-2번째 칸에서 11을 붙일 수도 있다고 생각할 수 있지만 그건 첫번째 조건에서 고려한 것이다.
하다가 좀 헷갈리면 주어진 방법들을 기준으로 그것들이 쓰일 수 있는 고유한 상황만 확인하면 된다. 따라서 두 상황에서 1가지 경우씩 현재로 올 수 있기 때문에
dp[i-1]*1 + dp[i-2]*1 이다.
근데 여기서 숫자가 너무 커지면 메모리 초과가 발생할 수 있다.(mod 값으로 답하라는 것이 힌트)
답을 다 dp에 계산하고 마지막에만 mod를 하는건 의미가 없다.
따라서 계산할때마다 mod를 계산해서 그 값을 넣어야 하는데, 그럼 값이 훼손되는게 아닌가 싶다.
이때는 mod의 성질을 살펴보면 되는데 (a+b)%m = ((a%m)+(b%m)%m)이다.
우리가 구해야하는 값은 dp[a+b]%m인데, dp 테이블에 값을 넣는 식과 비교해보면 dp 값에는 mod를 계산한 결과가 계속 들어가고 있고,
그 mod 계산 결과값을 더하고 또 mod를 하기 때문에 이는 ((a%m)+(b%m)%m)과 같다고 할 수 있다.
따라서 전혀 지장이 없다.
"""

View File

@@ -0,0 +1,55 @@
# 연속합
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = [0]+list(map(int, input().rstrip().split()))
prefix_sum = [0] * (n+1)
min_lst = [0]*(n+1)
prefix_sum[0] = 0
min_lst[0] = 0
now_min = 0
for i in range(1, n+1):
prefix_sum[i] = prefix_sum[i-1] + lst[i]
min_lst[i] = now_min
now_min = min(now_min, prefix_sum[i])
result = float('-inf')
for i in range(1, n+1):
result = max(result, prefix_sum[i]-min_lst[i])
print(result)
return
solution()
"""
걸린 시간: 30분
시간 복잡도: 모든 계산과 반복은 O(n)에 끝난다.
해설: 연속된 부분합을 편하게 다루기 위해 prefix_sum을 생각하였다.
그 후 prefix_sum들을 살펴보니, 모든 prefix_sum에 대해서 그 앞에 있는 것들 중 제일 작은 prefix_sum 빼면 최대가 나올 것 같았다.
그럼 그 구간의 제일 작은 것들을 매번 찾을 수는 없으니, prefix_sum을 계산하면서 제일 작은 것을 기록해야겠다고 생각했다.
그렇게 되면 계속 O(n)으로 다 처리할 수 있다.
제일 작은 것을 기록하려다보니 계산된 prefix_sum 중에 작은 것을 기록하려고 했는데, 사실 계산된 prefix_sum이 음수가 아닌 이상, 제일 작은 것은 0이 맞다.
따라서 계산된 prefix_sum 이전에 0을 넣고, 쭉 계산을 진행했다.
마지막 반복문으로 result를 모든 prefix_sum에 대해 그 구간의 최소값을 빼보며 result의 최대값을 구했다.
이 논리를 좀 더 직관적으로 말하는 풀이법이 있는 kadane라는 것이다.
dp[i]를 볼때 i번째 숫자로 새로 시작할지, 기존 최적 수열에 i를 붙일지 선택하는 것이다.
lst[i] > dp[i] + lst[i]라는 뜻은 지금까지 최선을 다해온 팀이 있는데, 내가 들어가는 것보다 나 혼자 시작하는게 더 나은 경우이다.
lst[i] < dp[i] + lst[i]라는 뜻은 내가 너무 멍청해서 팀원들이 플러스 역할을 해준다는 경우이다.
이 나혼자 새로 시작한다는 시점이 바로 내 풀이의 now_min이 바뀌는 시점이다. 너무 벌레를 만나서 그 벌레를 끊고 이후부터 새로 시작했다는 뜻이다.
여기서도 result를 계속 max값 업데이트를 해줘야하는데 관계를 끊고 새로 시작했다고 해도, 어떤 팀들은 이미 최고의 플레이를 했을 수도 있기 때문이다.
"""

View File

@@ -0,0 +1,39 @@
# 정수 삼각형
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
triangle = [[] for _ in range(n)]
for i in range(n):
triangle[i] = list(map(int, input().rstrip().split()))
dp = [[0] * i for i in range(1, n+1)]
dp[0][0] = triangle[0][0]
for i in range(1, n):
for j in range(len(dp[i])):
for k in range(2):
if j-k >= len(dp[i-1]) or j-k < 0:
continue
dp[i][j] = max(dp[i][j], dp[i-1][j-k]+triangle[i][j])
print(max(dp[n-1]))
return
solution()
"""
걸린 시간: 15분
시간 복잡도: 전체 수에 대해서 진행하기 때문에 O(n^2)이다.
해설: 1149번(RGB로 집 칠하기) 문제랑 비슷하다. 현재 값을 선택할 때 여기까지 올 수 있는 경우의 수 중 최선의 플레이를 고르고, 마지막에 마지막 라인 중에서
최선을 선택하는 방식이다. 1149번에서 설명했지만, 그리디와는 다르게 현재 선택한 수가 최선일지는 모르는 것이기 때문이다.
"""

View File

@@ -0,0 +1,45 @@
# 포도주 시식
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = [0] * n
for i in range(n):
lst[i] = int(input().rstrip())
dp = [0] * n
dp[0] = lst[0]
if n >= 2:
dp[1] = lst[0] + lst[1]
if n >= 3:
dp[2] = max(lst[0]+lst[1], lst[1]+lst[2], lst[0]+lst[2])
for i in range(3, n):
dp[i] = max(dp[i-1], dp[i-2]+lst[i], dp[i-3]+lst[i-1]+lst[i])
print(dp[n-1])
return
solution()
"""
걸린 시간: 10분
시간 복잡도: n개의 dp 테이블을 채워야하기 때문에 O(n)이다.
해설: 2579(계단 오르기) 문제와 비슷하다.
다른 점은 1,2칸씩 가는 것이 강제되어 있지 않고, 자유롭게 칸을 왔다갓다 할 수 있다는 것이다.
하지만 최대로 마셔야하기 때문에 왔다갔다 할 필요는 없고, 3개가 연속되지 않게 최대한 촘촘하게 먹어야 한다.
dp[i]는 i번째 값을 선택할 경우와 선택하지 않을 경우에 따라 max 값을 선택하면 되고, 선택하지 않을 경우 dp[i-1]에서 가져오고,
선택할 경우, 바로 앞의 잔을 마셨다면 2번째 잔을 마시지 말았어야하고(dp[i-3]+lst[i-1]+lst[i]),
바로 앞의 잔을 안 마셨다면 2번째 전에서 값(dp[i-2]+lst[i])을 가져오면 된다.
"""

View File

@@ -0,0 +1,34 @@
# 이친수
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n):
dp[i] = dp[i-2]+dp[i-1]
print(dp[n-1])
return
solution()
"""
걸린 시간: 11분
시간 복잡도: dp 테이블 n개를 채우면 되기 때문에 O(n)이다.
해설: 현재 칸을 볼 때 0을 넣을 거면 앞이 뭐든 상관없다. 따라서 dp[i-1]을 그대로 가져오면 된다.
반면에 1을 넣을 거면 앞이 무조건 0이어야 한다. 따라서 01을 쌍으로 넣어야 하고, 1904번의 문제에서 01과 0 타일로 채우는 것과 같은 문제이다.
따라서 두 칸을 통제해야하기 때문에 dp[i-2]개수만큼인 것이다.
따라서 dp[i] = dp[i-1] + dp[i-2]이다.
현재 칸이 0이거나 1이거나로 완전히 케이스를 분리했기 때문에 중복은 없다.
"""

View File

@@ -0,0 +1,40 @@
# 계단 오르기
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
score = [0] * (n+1)
for i in range(1, n+1):
score[i] = int(input().rstrip())
dp = [0] * (n+1)
dp[1] = score[1]
if n >= 2:
dp[2] = score[1] + score[2]
for i in range(3, n+1):
dp[i] = max(dp[i-3]+score[i-1]+score[i], dp[i-2]+score[i])
print(dp[n])
return
solution()
"""
걸린 시간: 20분
시간 복잡도: 각 계단을 한번씩 확인하며 dp를 채우기 때문에 O(n)이다.
해설: 현재 보는 계단을 오려면 어떻게 올 수 있는지 생각했을 때 방향이 한쪽으로 정해져있다. 무조건 올라오는 것이기 때문에 dp를 쓰면 된다.
현재 칸으로 올 수 있는 경우의 수는 2칸 전에서 올라오는 것과 1칸 전에서 올라오되, 그 전에 1칸 점프가 아니어야 한다.(3칸연속 방지)
따라서 2번째 경우의 수는 2번째 전 상황까지 강제를 해줘야 한다. 따라서 dp[i-3]+score[i-1]+score[i]가 되는 것이다.
또한 i-3을 접근하려면 i가 최소 3부터여야 하기 때문에 dp[1], dp[2]까지는 수동으로 설정해줘야 한다.
마지막으로 n은 자연수이므로 1일수도 있다. 이때는 dp[2] 설정이 안되므로 조건문을 달아준다.
"""

View File

@@ -0,0 +1,55 @@
# 삼각 그래프
import sys
input = sys.stdin.readline
def solution():
t = 1
while 1:
n = int(input().rstrip())
if n == 0:
break
g = [[] for _ in range(n)]
for i in range(n):
g[i] = list(map(int, input().rstrip().split()))
dp = [[float('inf') for _ in range(3)] for _ in range(n)]
dp[0][:2] = g[0][:2]
dp[0][2] = dp[0][1]+g[0][2]
for i in range(1, n):
for j in range(3):
if i == 1:
if j == 0:
dp[i][j] = dp[i-1][1]+g[i][j]
else:
dp[i][j] = min(dp[i][j-1], min(dp[i-1][1:]))+g[i][j]
else:
if j == 0:
dp[i][j] = min(dp[i-1][0], dp[i-1][1])+g[i][j]
elif j == 1:
dp[i][j] = min(dp[i][0], min(dp[i-1]))+g[i][j]
else:
dp[i][j] = min(dp[i][1], dp[i-1][j-1], dp[i-1][j])+g[i][j]
print(f'{t}. {dp[n-1][1]}')
t += 1
return
solution()
"""
걸린 시간: 30분
시간 복잡도: n*3 테이블을 모두 채우는 것이기 때문에 O(3n)이다.
해설: 1149번(RGB 집 칠하기) 문제처럼 현재 보는 수까지 오는 최적을 계속 계산하면 된다.
조금 다른 점은 분기 조건이 다르다는 점인데, 오른쪽, 아래, 오른쪽 아래, 왼쪽 아래, 이렇게 총 4가지 방향으로 이동할 수 있다.
또 하나의 고려할 점은 노드 값에 음수도 가능하기 때문에 여러 노드를 거치는 것이 꼭 손해는 아니라는 것이다.
따라서 이것에 맞게 최소값을 찾아와야 하고, 1,2,3열에 따라 올 수 있는 곳이 다르기 때문에 잘 지정해서 해야한다.
또한 시작인 [0][1]이라는 점에 의해서 0,1 행의 노드들은 특수성이 있기 때문에 이 부분들은 직접 체크하고 반복문을 돌리는 것이 낫다.
"""

View File

@@ -0,0 +1,36 @@
# 1,2,3 더하기
import sys
input = sys.stdin.readline
def solution():
t = int(input().rstrip())
dp = [0] * (11)
dp[0] = 1
dp[1] = 1
dp[2] = 2
for i in range(3, 11):
dp[i] = dp[i-1]+dp[i-2]+dp[i-3]
for _ in range(t):
n = int(input().rstrip())
print(dp[n])
return
solution()
"""
걸린 시간: 20분
시간 복잡도: n개의 dp 테이블을 한번만 채우면 되기 때문에 O(n)이다.
해설: 이 문제는 순서가 다른 것은 다르게 보는 순열이다. 따라서 순서를 고정하지 않고, 자유도를 주어서 현재 보는 수로 오기 위해 +1, +2, +3을 한 수들의
모든 경우를 다 더하면 된다.
반면 순서가 달라도 같은 것으로 보는 조합의 경우에는 순서를 고정시켜줘야 한다. 따라서 1만 썼을 때 경우의 수를 다 계산하고, 2를 썼을 때 경우, 3을 썼을 때 경우,
이렇게 나누면 순서가 고정되어 자동으로 조합 계산을 한 것처럼 된다.
"""

View File

@@ -0,0 +1,32 @@
# 파도반 수열
import sys
input = sys.stdin.readline
def solution():
t = int(input().rstrip())
dp = [0] * 102
dp[:5] = [1,1,1,2,2]
for i in range(5, 102):
dp[i] = dp[i-5]+dp[i-1]
for _ in range(t):
n = int(input().rstrip())
print(dp[n-1])
return
solution()
"""
걸린 시간: 17분
시간 복잡도: n개의 dp 테이블을 채워야하기 때문에 O(n)이다.
해설: 나선모양으로 2개의 변을 계속 더하기 때문에 어디 값 2개가 더해지는지 규칙을 파악하면 된다.
따라서 dp[i] = dp[i-5]+dp[i-1]인 것을 알 수 있고, 반복문을 위해 dp[4]까지는 구해놔야 dp[5]부터 시작할 수 있다.
"""

View File

@@ -0,0 +1,37 @@
# 스티커
import sys
input = sys.stdin.readline
def solution():
t = int(input().rstrip())
for _ in range(t):
n = int(input().rstrip())
lst = [list(map(int, input().rstrip().split())) for _ in range(2)]
dp = [[0]*n for _ in range(2)]
dp[0][0] = lst[0][0]
dp[1][0] = lst[1][0]
for i in range(1, n):
dp[0][i] = max(dp[0][i-1], dp[1][i-1]+lst[0][i])
dp[1][i] = max(dp[1][i-1], dp[0][i-1]+lst[1][i])
print(max(dp[0][n-1], dp[1][n-1]))
return
solution()
"""
걸린 시간: 20분
시간 복잡도: 2*n 테이블을 채워야하기 때문에 O(2n)이다.
해설: 한 열에서 동시에 선택을 할 수는 없으므로, 각 행마다 경우의 수를 구해가야 하고, 한 행이 생길때마다 선택할 수 있는 경우를 보면 된다.
하나의 값을 볼때, 이걸 선택한다면 이전 열의 다른 행에서 와야하고, 선택하지 않는다면 이전 열의 같은 행에서 오면 된다.
"""

View File

@@ -0,0 +1,54 @@
# 좋다
import sys
input = sys.stdin.readline
def two_pointer(l, r, nums, i, target):
while l < r:
if l == i:
l += 1
continue
if r == i:
r -= 1
continue
now = nums[l] + nums[r]
if now == target:
return True
if now < target:
l += 1
else:
r -= 1
return False
def solution():
n = int(input().rstrip())
nums = list(map(int, input().rstrip().split()))
sorted_nums = sorted(nums)
count = 0
for i in range(n):
if two_pointer(0, n-1, sorted_nums, i, sorted_nums[i]):
count += 1
print(count)
return
solution()
"""
걸린 시간: 40분
시간 복잡도: 모든 범위에 대해 투 포인터는 O(n)이고, 모든 숫자가 타겟이 되기 때문에 O(n^2)이다.
해설: 모든 것을 확인해야 하는 여부를 생각해봤을 때, 어떤 수가 있을지 모르기 때문에 다 봐야한다.
그럼 두 개의 조합을 모두 봐야 하나?를 생각했을 때 정렬해서 투 포인터를 쓰면 다 보지 않아도 된다.
이제 범위를 본인보다 작은 것들에서 투포인터를 진행하며 구현을 했다.
근데 틀렸다. 왜 why? 수가 자연수만 있는 것이 아니었다. 당연히 자연수 범위라고 생각했는데 알고보니 절대값으로 범위가 표현되어 있었다.
음수와 양수를 합치면 값이 줄어들기 때문에 타겟 숫자의 오른쪽도 후보가 될 수 있어서 모든 범위에 대해 계속 투포인터를 진행해야 한다.
"""

View File

@@ -0,0 +1,80 @@
# A와 B 2
import sys
from collections import deque
input = sys.stdin.readline
def check_same(dq, start, lst_s):
if start == -1:
dq = list(reversed(dq))
for i in range(len(dq)):
if dq[i] != lst_s[i]:
return 0
else:
return 1
def solution():
s = input().rstrip()
t = input().rstrip()
lst_s = list(s)
dq = deque(list(t))
result = []
def bt(now_dq, start, end):
if len(now_dq) == len(lst_s):
result.append(check_same(now_dq, start, lst_s))
return
if now_dq[start] == "A":
if now_dq[end] == "B":
return
else:
tmp = now_dq.pop() if start == 0 else now_dq.popleft()
bt(now_dq, start, end)
now_dq.append(tmp) if start == 0 else now_dq.appendleft(tmp)
elif now_dq[start] == "B":
if now_dq[end] == "A":
tmp = now_dq.pop() if start == 0 else now_dq.popleft()
bt(now_dq, start, end)
now_dq.append(tmp) if start == 0 else now_dq.appendleft(tmp)
start, end = end, start
tmp = now_dq.pop() if start == 0 else now_dq.popleft()
bt(now_dq, start, end)
now_dq.append(tmp) if start == 0 else now_dq.appendleft(tmp)
start, end = 0, -1
bt(dq, start, end)
if 1 in result:
print(1)
else:
print(0)
return
solution()
"""
걸린 시간: 1시간 20분
시간 복잡도: deque는 연결 리스트라서 i번째 원소에 접근 시간이 O(n)이다.(충격적인 사실)
따라서 check_same이 O(n^2)이 될 수 있고, 이대로 bt까지 해버리면 시간복잡도는 어후... 최대 길이가 50이라 살았다.
해설: s에서 t를 가려고 하니 2가지 경우의 수를 49번까지 해야하기 때문에 2^49라서 말이 안된다고 생각했다.
그래서 t에서 s로 가려고 생각하다보니 시작 끝이 A-A, A-B, B-A, B-B에 따라 경우의 수가 많이 없을 것 같다고 생각했다.
각각 현재 상황에 올 수 있는 이전 상황을 세팅해보면,
A-A: 맨 앞이 A였고, A를 붙임(경우 1)
A-B: 있을 수 없는 상황(B를 붙이면 뒤집어야하기 때문 -> 경우 0)
B-B: 맨 앞이 B였고, B를 붙임(경우 1)
B-A: 맨 앞 A였고 B를 붙임 or 맨 앞 B였고, A를 붙임(경우 2)
처음에는 B-A도 경우가 1인줄 알고 단순 구현으로 해봤는데 틀렸다.
알고보니 B-A에서 경우가 2가지였고, 이때부터 백트래킹을 고민하였다.
4가지 상황에 대해 각각 분기해서 bt를 진행하였고, 슬라이싱으로 넘기는 것은 시간복잡도가 자른만큼 생기기 때문에 deque를 이용해서
빼고 bt하고 다시 뺐던거 넣는 방식으로 기존 dq를 유지하면서 bt를 진행했다.
근데 각 상황에 대해 분기처리를 하면 코드 중복이 좀 많이 발생하여 4가지 상황을 자세히 살펴보니
끝이 A일때 pop으로 bt 진행하고, 시작이 B일때 popleft로 bt 진행하면 그냥 모든 조건을 맞출 수 있었다.
"""

View File

@@ -0,0 +1,57 @@
# 숨바꼭질 3
import sys
from collections import deque
input = sys.stdin.readline
MAX_LINE = 100000
def solution():
n, k = map(int, input().rstrip().split())
move = [1, -1]
visited = [float('inf')] * (MAX_LINE+1)
dq = deque([n])
visited[n] = 0
while dq:
now = dq.popleft()
if now == k:
break
teleport = now*2
while 0 <= teleport <= MAX_LINE and visited[teleport] > visited[now]:
dq.append(teleport)
visited[teleport] = visited[now]
teleport = teleport*2
for m in move:
moved = now+m
if 0 <= moved <= MAX_LINE and visited[moved] > visited[now]+1:
dq.append(moved)
visited[moved] = visited[now]+1
print(visited[k])
return
solution()
"""
걸린 시간: 39분
시간 복잡도: 한 노드를 pop했을 때 많아도 log(100000)정도이기 때문에 n=100000일때 O(nlogn)정도이다.
해설: 뱀과 사다리(16928)랑 비슷한 문제인 것 같다. 현재 위치에서 순간이동하는 곳은 같은 시간으로 넣어야 하고, 그 곳을 visited를
만드는 방식으로 진행하는 것이었다. 하지만 이 문제는 순간이동 위치가 1:1 대응이 아니고, 뒤로 순간이동 하는 것은 없다는 점에서 차이가 있다.
하지만 일단 중요한 건 bfs 레벨에서 순간이동에 대응되는 위치들이 다 같은 레벨이기 때문에 얘네를 큐에 순서대로 잘 넣는 것이다.
그렇게 했는데 99퍼센트에서 틀렸다고 나왔다.
생각해보니까 앞뒤로 이동해서 이미 방문해버린 것을 같은 턴에 순간이동으로 올 수 있었다면 더 나은 방법으로 올 수 있음.
예를 들어 4 6의 경우 4 8 16 .. 5 3 일때 5에서 6을 +1로 방문했는데, 사실 3에서 6으로 순간이동하면 +1 없이 갈 수 있다.
결국에는 또 같은 레벨의 구분을 제대로 하지 못했다.
일단 대안으로는 레벨 조정이 있겠지만 그냥 방문 기준으로 노드를 넣지 말고, 다 계산해봐서 지금 값보다 더 좋은 방향이 있다면 큐에 다시 추가하도록
조정하였다.
"""

View File

@@ -0,0 +1,57 @@
# 빗물
import sys
input = sys.stdin.readline
def solution():
h, w = map(int, input().rstrip().split())
blocks = map(int, input().rstrip().split())
stack = []
result = 0
for b in blocks:
if stack:
if b < stack[0]:
stack.append(b)
continue
else:
while stack:
if len(stack) == 1:
stack.pop()
break
result += stack[0] - stack.pop()
stack.append(b)
else:
stack.append(b)
while stack:
if len(stack) == 1:
stack.pop()
break
now = stack.pop()
while stack and stack[-1] <= now:
result += now - stack.pop()
print(result)
return
solution()
"""
걸린 시간: 50분
시간 복잡도: 모든 블럭이 stack에 한번씩 들어갔다가 나오기 때문에 O(n)이다.
해설: 이전에 풀었던 지붕 쌓기와 비슷한 문제이다. 이런 문제들에서 시간을 뺐기지 않고 가려면 좀 공식화해둘 필요가 있다.
내가 기억해야하고 기억하지 않아도 될 것들을 구분해야 하는데,
(1) 시작 블럭의 높이를 기준으로 동등하거나 그 이상의 블럭이 나오면 지금까지 나왔던 것들은 시작 블럭 기준으로 계산하고, 잊어도 됨
1번을 진행하면서 stack의 내용물은 pop되고 새로운 시작 기준점이 잡힌다.
(2) 시작 블럭의 높이를 기준으로 미만인 것들만 있다면 pop될 일이 없기 때문에 다 끝나면 stack에 내용물이 있을 것이다.
이제 stack을 다 비우면서 모든 블럭에 대한 계산을 진행해야 한다.
이때부터는 1번의 역순이다. 왜 why? 시작 블럭 기준으로 미만인 것들만 있었다는 것은 오르내리막이 있어도 최종적으로는 내리막이었다는 뜻이고,
이는 거꾸로 보면 1번에서 하던것처럼 동등하거나 그 이상의 블럭이 무조건 있다는 뜻이다.
따라서 기준점을 stack[-1]로 잡고 1번 방식을 진행하면 된다.
"""

View File

@@ -0,0 +1,48 @@
# 1, 2, 3 더하기 4
import sys
input = sys.stdin.readline
def update_dp(start, n, dp):
for i in range(start, n+1):
dp[i] = (sum(dp[i-1]), sum(dp[i-2][1:]), sum(dp[i-3][2:]))
return
def solution():
t = int(input().rstrip())
dp = [-1] * 10001
dp[1], dp[2], dp[3] = (1, 0, 0), (1, 1, 0), (2, 0, 1)
start = 4
for _ in range(t):
n = int(input().rstrip())
if dp[n] == -1:
update_dp(start, n, dp)
start = n+1
print(sum(dp[n]))
return
solution()
"""
걸린 시간: 1시간
시간 복잡도: dp[n]까지 채우는데 한번 채울때 O(1)이므로 테스트케이스 중에 가장 큰 n만큼 시간이 걸린다. O(max(n))
해설: 몇 가지 n에 대해 모든 경우의 수를 만들어보니,
dp[n]을 구할 때 이전 숫자들의 경우에다 1, 2, 3 을 더해서 할 수 있겠다고 생각했다.
n-1, n-2, n-3에서 1, 2, 3의 숫자를 더해서 만들고, 중복되는 것들을 없애기 위해 규칙을 파악했다.
1 > 2 > 3 으로 우선순위를 두고, 각각 포함된 것들의 개수를 기록해나간다.
1 0 0, 1 1 0, 2 0 1, 3 1 0, 4 1 0, 5 1 1 ... 이 다음 것을 찾아보자
n-1에서 모든 경우에 1을 더해주면 되기 때문에 5+1+1 = 7이 된다.
n-2에서 모든 경우에 1을 추가한 것이 이미 n-1에 있다. 따라서 1을 안 쓴 2, 3 그룹의 경우에만 2를 더해주면 되기 때문에 1+0=1이 된다.
n-3도 n-2 원리를 적용하여 3 그룹의 경우에만 3을 더해주면 0=0이 된다.
따라서 7 1 0 이되고, 숫자 7의 전체 경우의 수는 8개이다.
근데 이렇게 안하고 더 쉬운 방법이 있다.
1만 쓴 경우, 2도 쓴 경우, 3도 쓴 경우로 도구를 늘려가면서 dp를 갱신하는 방법이다.
1만 쓴 경우는 당연히 다 1이다.
2를 추가하게 되면 dp[i] += dp[i-2]로 현재 숫자-2에서 나온 모든 경우의 수의 끝에 +2를 붙이는 것이다. 3도 마찬가지.
"""

View File

@@ -0,0 +1,87 @@
# 뱀과 사다리 게임
import sys
from collections import deque
input = sys.stdin.readline
def solution():
n, m = map(int, input().rstrip().split())
g = [-1] * 101
visited = [-1] * 101
for _ in range(n+m):
v, w = map(int, input().rstrip().split())
g[v] = w
q = deque([1])
visited[1] = 0
while q:
now = q.popleft()
for k in range(1, 7):
nxt = now+k
if nxt > 100:
continue
if g[nxt] != -1:
nxt = g[nxt]
if visited[nxt] == -1:
q.append(nxt)
visited[nxt] = visited[now]+1
print(visited[100])
return
solution()
"""
걸린 시간: 몰라
시간 복잡도: 각 칸당 한번씩만 방문하기 때문에 O(100)이다.
해설: 1697-s1과 같은 실수를 반복했다. 앞 뒤로 갈 수 있는 것은 dp로 할때 어디부터 채워야할지 모르기 때문에 bfs로 해야했거늘..
현재 칸에서 주사위를 굴려서 6칸을 다 갈 수 있고, 이때 사다리나 뱀이 있으면 그곳으로 바로 이동해야 한다.
근데 나는 일단 6칸을 다 방문처리하고, now에서 사다리나 뱀이 있으면 방문하는 방식으로 진행을 했다.
하지만 이렇게 하면 사다리를 타고 올라가는 곳이 레벨이 1이라고 할때, 주사위로 가서 한 번 더 간 칸, 즉 레벨 2의 칸이 큐에 먼저 들어간다.
레벨이 꼬여버리기 때문에 주사위로 온 칸이 사다리가 연결되어 있다면 바로 이동해서 그 칸을 큐에 넣는 것이 순서가 맞다.
주사위로 나온 칸은 그럼 계속 방문처리가 안되는 것 아니냐라고 생각할 수 있지만, 실제로 방문처리가 안되기도 하고
그 칸은 항상 사다리로 이동한 칸과 같은 취급을 받기 때문에 상관없다.
이렇게 최단거리 문제를 bfs로 풀때는 레벨을 잘 따져야 할 것 같다.
"""
# def dp_up(dp, g): # 30분
# for i in range(2, 7):
# for j in range(i-1, 0, -1):
# dp[i] = min(dp[i], dp[j]+1)
# dp[i] = min(dp[i], dp[g[i]] if g[i] != -1 else float('inf'))
# for i in range(7, 101):
# dp[i] = min(dp[i-1]+1, dp[i-2]+1, dp[i-3]+1, dp[i-4]+1, dp[i-5]+1, dp[i-6]+1, dp[g[i]] if g[i] != -1 else float('inf'))
# return
# def dp_down(dp, g):
# for i in range(100, 1, -1):
# dp[i] = min(dp[i-1]+1, dp[i-2]+1, dp[i-3]+1, dp[i-4]+1, dp[i-5]+1, dp[i-6]+1, dp[g[i]] if g[i] != -1 else float('inf'))
# return
# def solution():
# n, m = map(int, input().rstrip().split())
# g = [-1] * 101
# dp = [float('inf')] * 101
# dp[1] = 0
# for _ in range(n+m):
# v, w = map(int, input().rstrip().split())
# g[w] = v # 도착점이 key, 시작점이 value
# dp_up(dp, g)
# dp_down(dp, g)
# dp_up(dp, g)
# print(dp[100])
# # print(dp)
# # print("70:", dp[70], "72:", dp[72], "67:", dp[67], "68:", dp[68], "98:", dp[98])
# solution()

View File

@@ -0,0 +1,47 @@
# 부분합
import sys
input = sys.stdin.readline
def solution():
n, s = map(int, input().rstrip().split())
lst = list(map(int, input().rstrip().split()))
l, r = 0, 0
now = lst[0]
result = 100001
while l <= r:
if now >= s:
while now >= s:
now -= lst[l]
l += 1
result = min(result, r-(l-1)+1)
if r >= n-1:
break
if now < s:
r += 1
now += lst[r]
if result > 100000:
result = 0
print(result)
return
solution()
"""
걸린 시간: 18분
시간 복잡도: 두 포인터가 모든 원소를 가르키는 것이 최악이므로 O(n)이다.
해설: 투 포인터를 활용하여 오른쪽으로 값을 추가하고, 왼쪽으로 값을 제외한다.
현재 값이 s보다 크면, s보다 값이 작아질때까지 왼쪽 포인터를 오른쪽으로 옮기면서 값을 뺀다.
현재 값이 s보다 작으면 오른쪽 포인터를 오른쪽으로 옮기면서 값을 더한다.
마지막까지 result 값이 100000보다 크면 답이 없는 것이므로 result를 0으로 만든다.
"""

View File

@@ -0,0 +1,51 @@
# 여행가자
import sys
from collections import deque
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
m = int(input().rstrip())
g = {}
for i in range(n):
info = list(map(int, input().rstrip().split()))
g[i+1] = [idx+1 for idx, con in enumerate(info) if con == 1]
plan = list(map(int, input().rstrip().split()))
visited = [0] * (n+1)
q = deque([plan[0]])
visited[plan[0]] = 1
while q:
now = q.popleft()
for city in g[now]:
if visited[city]:
continue
q.append(city)
visited[city] = 1
for city in plan:
if visited[city] == 0:
print("NO")
break
else:
print("YES")
return
solution()
"""
걸린 시간: 17분
시간 복잡도: 한 노드당 큐에 한번만 들어오고 나가기 때문에 O(n)인데, 문제는 시작할 때 각 노드마다 연결된 데이터를 읽는 것이
O(n^2)이다.
해설: 뭐가 됐든 시작 도시를 기준으로 하나의 그룹으로 묶여 있으면 된다. 즉 bfs를 진행해서 한 묶음에만 있으면 된다.
다른 풀이로 union-find도 있다.
공통 조상으로 묶는 방식인데, find 함수는 parent 리스트에서 본인이 parent가 아니면 계속 재귀로 최초 조상을 찾는 것이다.
union은 두 노드의 최초 조상을 찾아서 다르면 한쪽으로 편입 시키는 방식이다. 근데 이것도 O(n^2)이다.
"""

View File

@@ -0,0 +1,58 @@
# 알파벳
import sys
input = sys.stdin.readline
def solution():
r, c = map(int, input().rstrip().split())
grid = [[1 << (ord(ch)-65) for ch in input().rstrip()] for _ in range(r)]
dr = [1, -1, 0, 0]
dc = [0, 0, 1, -1]
result = 0
def dfs(now_r, now_c, mask, depth):
is_last = True
for i in range(4):
nxt_r, nxt_c = now_r + dr[i], now_c + dc[i]
if 0 <= nxt_r < r and 0 <= nxt_c < c:
now_bit = grid[nxt_r][nxt_c]
if mask & now_bit:
continue
dfs(nxt_r, nxt_c, mask | now_bit, depth+1)
is_last = False
if is_last:
nonlocal result
result = max(result, depth)
return
dfs(0, 0, grid[0][0], 1)
print(result)
return
solution()
"""
걸린 시간: 1시간
시간 복잡도: 최대로 멀리 갈 수 있는게 알파벳 개수인 26개에 매번 경우의 수는 4이므로 O(4^26)이지만
방문한 것들은 안가고 그러면 저것보다는 작을 것 같다.
해설: 처음에는 아무생각 없이 bfs로 진행했다가 갈 수 있는 거리의 최대치를 생각해보니까 dfs로 다 봐야할 것 같았다.
visited는 set으로 관리하면서 copy를 해서 넘겨주는 식으로 재귀 dfs를 했는데(사실상 백트래킹이지..) 시간초과가 났다.
생각해보니 계속 copy를 하면 시간이 너무 걸릴 것 같아서 백트래킹 정석대로 dfs 전에 add, 후에 remove 하는 방식으로 했다.
질문 게시판을 보면 이렇게 하면 다 풀린다던데 네트워크가 느려서일 것 같았다.
일단 더 빠른 방법을 알아봤는데 비트마스크로 하면 더 빠르다고 한다.
그래서 비트마스크로 진행을 했고, 이때는 숫자를 넘겨주는 것이니까 복원 과정 필요 없이 업데이트된 숫자만 넘겨주면 된다.
근데 이렇게 해도 시간초과가 걸려서 네트워크 문제가 너무 심하다고 생각하며 끝냈다.
앞으로도 순서가 명확하고 사용 유무 같은 것을 기록할 일 있으면 비트마스크를 쓰면 좋을 것 같다. 물론 크기도 작은게 낫겠다.
"""

View File

@@ -0,0 +1,75 @@
# 컨베이어 벨트 위의 로봇
import sys
from collections import deque
input = sys.stdin.readline
def solution():
n, k = map(int, input().rstrip().split())
health = [-1] + list(map(int, input().rstrip().split()))
robot = [0] * (2*n+1)
robot_order = deque([])
front = deque([i+1 for i in range(n)])
back = deque([i for i in range(2*n, n, -1)])
unhealth_count = 0
result = 0
while unhealth_count < k:
result += 1
front.appendleft(back.popleft())
back.append(front.pop())
if robot[front[-1]] == 1:
robot_order.popleft()
robot[front[-1]] = 0
now_robot_len = len(robot_order)
for _ in range(now_robot_len):
idx = robot_order.popleft()
nxt = idx + 1
if idx == (2*n):
nxt = 1
if robot[nxt] == 0 and health[nxt] > 0:
robot[idx] = 0
robot[nxt] = 1
robot_order.append(nxt)
health[nxt] -= 1
if health[nxt] == 0:
unhealth_count += 1
else:
robot_order.append(idx)
if robot[front[-1]] == 1:
robot_order.popleft()
robot[front[-1]] = 0
if robot[front[0]] == 0 and health[front[0]] > 0:
robot_order.append(front[0])
robot[front[0]] = 1
health[front[0]] -= 1
if health[front[0]] == 0:
unhealth_count += 1
print(result)
return
solution()
"""
걸린 시간: 26분
시간 복잡도: 내구도가 1000까지이고, 이게 다 깎인 것이 k개가 될때까지이므로 O(1000*k)인데, 중간에 로봇이 한칸씩 움직이기 때문에
컨베이어벨트 윗부분인 n만큼 for문을 돈다. 근데 나는 로봇이 있는 칸만 확인하기 때문에 O(1000*k*n)보다는 적다.
해설: 문제조건대로 그대로 구현하면 된다. 벨트가 도는 것은 2개의 deque를 활용해서 구현하였고, 로봇이 있는 칸만 확인하기 위해 robot이 들어간 순서를
기록해놓는 robot_order를 queue로 구현한다. 각 단계를 구현하는 것은 기록할 것들만 잘 생각해서 빼먹지 않으면 어렵지 않게 가능하다.
또한 2n에서 다음 칸이 1이기 때문에 이것만 예외처리 하면 된다.
로봇을 올릴 경우 로봇을 내릴 경우
robot: 로봇 유무 | 1 | 0
health: 내구도 | -1 | -
robot_order: 로봇 올린 순서 | append(nxt) | popleft(now)
"""

View File

@@ -0,0 +1,48 @@
# 문자열 게임 2
import sys
input = sys.stdin.readline
def find_str(w, k):
result = {chr(i): [] for i in range(97, 123)}
ans1 = float("inf")
ans2 = -1
for i, c in enumerate(w):
result[c].append(i)
for lst in result.values():
for i in range(len(lst)-k+1):
ans1 = min(ans1, lst[i+k-1] - lst[i]+1)
ans2 = max(ans2, lst[i+k-1] - lst[i]+1)
return (ans1, ans2)
def solution():
t = int(input().rstrip())
for _ in range(t):
w = input().rstrip()
k = int(input().rstrip())
answer1, answer2 = find_str(w, k)
if answer2 == -1:
print(answer2)
else:
print(answer1, answer2)
return
solution()
"""
걸린 시간: 1시간 좀 넘게
시간 복잡도: 각 문자마다 슬라이딩 윈도우로 한번씩만 보기 때문에 O(n)이다.
해설: 전체 문자열을 한 번만 돌면서 문자마다 시작, 끝 인덱스와 개수 등을 저장하면서 진행하려고 했는데 한번에 하기에는
변하는 것이 너무 많아서 문자마다 인덱스를 저장하고 답을 구하기로 했다.
이렇게 해도 각 문자마다 한번씩만 보기 때문에 O(n)으로 괜찮다.
모든 알파벳에 대해 나오는 인덱스를 리스트에 순서대로 넣은 후 슬라이딩 윈도우로 K만큼에 있는 인덱스들의 차 + 1을 구한다.
이것이 문자열의 길이인데 3번은 최소값, 4번은 최대값을 구하면 된다.
"""

View File

@@ -0,0 +1,59 @@
# 비슷한 단어
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
now = {"": [(input().rstrip(), i) for i in range(n)]}
temp = {}
idx = 0
while 1:
if not now:
lst = list(temp.values())
print(lst[0][0][0])
print(lst[0][1][0])
break
temp = now.copy()
now = {}
for key in temp:
for word, num in temp[key]:
if idx >= len(word):
continue
chr = key+word[idx]
if now.get(chr, 0):
now[chr].append((word, num))
else:
now[chr] = [(word, num)]
for key in list(now.keys()):
if len(now[key]) == 1:
del now[key]
idx += 1
return
solution()
"""
걸린 시간: 오래 걸림
시간 복잡도: 모든 글자의 글자수를 보기 때문에 최대 글자수를 m이라고 하면 O(nm)이다. 근데 새로운 키를 만들 때 더하기를 하기 때문애 (O(nm*m))이다.
해설: 모든 단어들을 한글자씩 보면서 딕셔너리에 그 값을 키로 넣는다. 같은 글씨는 같은 키에 리스트로 모은다.
두번째 이상의 글자를 볼때는 이전에 있던 키에 현재 글자를 더한 것을 키로 만들어서 반복한다.
글자수가 다 다르기 때문에 현재 봐야하는 idx보다 글자 길이가 작으면 그냥 그 글자는 넘어간다.
그 후 각 키에 대해 모인 것들의 개수가 1이면 겹치는 녀석이 없는 것이기 때문에 키를 del 한다.
이때 del을 한 후 남은 것이 아무것도 없다면 그 녀석들이 최종 상태였으므로 그 녀석들을 가지고 와서 답을 낸다.
그 녀석들은 temp에 분해하기 전에 기록해놓는다.
계속 입력된 순서대로 꺼내서 진행했기 때문에 temp에 남은 첫 번째 리스트의 1,2번들이 답이다.
다른 방법으로 사전순으로 정렬하면 어느정도 prefix가 비슷한 애들끼리 모이기 때문에 인접한 것들끼리만 비교해서 최대 길이를 찾고,
그 길이만큼 prefix인 녀석들을 모아서 입력 순서가 가장 빠른 녀석 두 놈을 골라서 주면 된다.
정렬할 때 O(nlogn)에 글자수만큼 읽어야 되니까, 결국 정렬이 O(mnlogn)만큼 걸리고, 나머지 인접한 것 비교와 답 찾기는 O(nm)이면 된다.
따라서 전체는 O(mnlogn)
"""

View File

@@ -0,0 +1,74 @@
# 빌런 호석
import sys
input = sys.stdin.readline
def check_change_table(change_table, now_floor, target_floor):
count = 0
for i in range(len(now_floor)):
count += change_table[int(now_floor[i])][int(target_floor[i])]
return count
def count_diff(led1, led2):
count = 0
for i in range(len(led1)):
if led1[i] != led2[i]:
count += 1
return count
def fill_change_table(change_table, led):
for i in range(10):
for j in range(10):
if i == j:
continue
if change_table[i][j] != 0:
continue
change_table[i][j] = count_diff(led[i], led[j])
return
def solution():
led = [[0]*7 for _ in range(10)]
led[0] = [1, 1, 1, 0, 1, 1, 1]
led[1] = [0, 0, 1, 0, 0, 1, 0]
led[2] = [1, 0, 1, 1, 1, 0, 1]
led[3] = [1, 0, 1, 1, 0, 1, 1]
led[4] = [0, 1, 1, 1, 0, 1, 0]
led[5] = [1, 1, 0, 1, 0, 1, 1]
led[6] = [1, 1, 0, 1, 1, 1, 1]
led[7] = [1, 0, 1, 0, 0, 1, 0]
led[8] = [1, 1, 1, 1, 1, 1, 1]
led[9] = [1, 1, 1, 1, 0, 1, 1]
change_table = [[0]*10 for _ in range(10)]
fill_change_table(change_table, led)
n, k, p, x = map(int, input().rstrip().split())
result = 0
now_floor = "0"*(k-len(str(x))) + str(x)
for i in range(1, n+1):
if i == x:
continue
target_floor = "0"*(k-len(str(i))) + str(i)
if check_change_table(change_table, now_floor, target_floor) <= p:
result += 1
print(result)
return
solution()
"""
걸린 시간: 46분
시간 복잡도: 필요한 반전 횟수를 기록한 테이블을 만드는데 상수시간이다.
모든 층에 대해 반전횟수 테이블을 참조하여 계산해보기 때문에 O(n)이다.
해설: led가 켜진 곳은 1, 꺼진 곳은 0으로 해서 두 수를 비교했을 때 다른 숫자인 곳이 필요한 반전 횟수이다.
반전횟수 테이블을 가지고 현재 층에서 모든 층을 타겟으로 p보다 반전 횟수가 적은 것의 개수가 답이다.
"""

View File

@@ -0,0 +1,50 @@
# 용액
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
lst = list(map(int, input().rstrip().split()))
result = float('inf')
answer = -1
l, r = 0, len(lst)-1
while l < r:
now = lst[l] + lst[r]
if abs(now) < result:
result = abs(now)
answer = (lst[l], lst[r])
result = min(result, abs(now))
if now == 0:
break
if now > 0:
r -= 1
else:
l += 1
print(*answer)
return
solution()
"""
걸린 시간: 33분
시간 복잡도: 모든 수를 한번씩 보기 때문에 O(n)이다.
해설: 모든 경우의 수를 확인해보는 것은 너무 많기 때문에 적절히 줄여서 볼 것만 확인해야 한다.
그 생각을 하게 되면 슬라이딩 윈도우와 투 포인터를 떠올릴 수 있다.
그 다음 두 가지 포인터를 어떤 상황에서 뭘 움직일지를 정하면 되는데, 0, 1에서 오른쪽으로 출발한다고 하면
리스트가 오름차순이기 때문에 어떨때 뭘 옮겨야할지 애매해진다.
따라서 0, n-1에서 시작하면 l포인터는 오른쪽으로 가면 숫자가 커지고, r포인터는 왼쪽으로 가면 숫자가 작아지기 때문에
0보다 클 경우 숫자를 줄여야되니까 r포인터를 움직이고, 반대의 경우 l포인터를 움직이도록 설정하면 된다.
-100 -2 -1 103의 경우에서 처음이 -100 103으로 3이고, 양수이므로 r을 왼쪽으로 움직여서 -100과 -1로 -101을 만든다.
이때 -2와 103은 평생 볼일이 없는데 당연하게도 3보다 커질 것이 자명하므로 안 봐도 된다.
모든 a[i]에 대해서 이분탐색을 통해 -a[i]와 가장 가까운 수를 찾는 방식으로도 해결할 수 있다. -> 이건 O(nlogn)
"""

View File

@@ -0,0 +1,33 @@
# 탑
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
tops = list(map(int, input().rstrip().split()))
s = []
result = [0] * n
for i in range(n-1, -1, -1):
while s and tops[i] >= s[-1][1]:
idx, _ = s.pop()
result[idx] = i+1
s.append((i, tops[i]))
print(*result)
return
solution()
"""
걸린 시간: 17분
시간 복잡도: 한 탑당 한번씩 stack에 들어가고 나오기 때문에 O(n)이다.
해설: 왼쪽부터 확인하면서 stack에 넣고, stack 있는 것들이 현재 보는 것보다 작은 것은 다 빼면
그게 stack 있는 애들한테 처음으로 만나는 가장 크거나 같은 탑이다. 그리고 그 탑이 신호를 수신한다는 뜻이다.
stack에 남아있는 애들은 본인보다 큰 것을 보지 못한 애들이기 때문에 기존 초기화에서 했던 0으로 냅두면 된다.
"""

View File

@@ -0,0 +1,55 @@
# 줄세우기
import sys
input = sys.stdin.readline
def binary_search(v, tails):
l, r = 0, len(tails)
while l < r:
mid = (l+r)//2
if tails[mid] >= v:
r = mid
else:
l = mid + 1
return l
def solution():
n = int(input().rstrip())
tails = [int(input().rstrip())]
for _ in range(n-1):
now = int(input().rstrip())
if tails[-1] < now:
tails.append(now)
elif tails[-1] == now:
continue
else:
idx = binary_search(now, tails)
tails[idx] = now
print(n - len(tails))
return
solution()
"""
걸린 시간: 못 품
시간 복잡도: O(nlogn)
해설: 최대 길이의 이미 정렬되어 있는 애들을 제외한 애들을 움직이면 된다. -> LIS(가장 긴 증가하는 부분 수열)
두 가지 방법이 있는데,
1) dp[i]는 i번째가 맨 끝인 증가하는 부분 수열의 길이라고 할 때, 이중 for문으로 dp[i]를 볼때 j<i인 arr[j]와 dp[j]를 보면서 dp[i]를 갱신하는 방법이다.
-> O(n^2)
2) tails[i]는 i+1 길이의 증가하는 부분 수열들 중에 맨 끝값 중 최소값이라고 할 때(2 5 8, 2 4 6이 있으면 tails[2]=6),
지금 보는 값이 tails[-1]보다 크다면 tails.append()로 최장 증가 부분 수열의 고점을 높이고,
지금 보는 값이 tails[-1]보다 같다면 그냥 넘어가고,
지금 보는 값이 tails[-1]보다 작다면 tails의 어디에 들어갈지 이분탐색으로 확인해서,
그 인덱스의 값을 교체함으로써 tails[idx]에 최대한 작은 수를 놓고 앞으로의 가능성을 높인다.(tails=[2, 5, 8] 이때 4가 들어오면 tails=[2, 4, 8])
이렇게 하면 최장 증가 부분 수열의 길이를 O(nlogn)에 구할 수 있지만, 실제 수열은 모른다. -> 따로 기록해두어야 함.
"""

View File

@@ -0,0 +1,63 @@
# 숫자 고르기
import sys
input = sys.stdin.readline
def dfs(v, g, n):
visited = [0] * (n+1)
stack = [v]
record = [v]
while stack:
now = stack.pop()
if visited[now]:
break
visited[now] = 1
w = g[now]
stack.append(w)
record.append(w)
target = record.pop()
result = set()
result.add(target)
while record and record[-1] != target:
result.add(record.pop())
return result
def solution():
n = int(input().rstrip())
g = {}
result = set()
for i in range(1, n+1):
g[i] = int(input().rstrip())
for i in range(1, n+1):
if i in result:
continue
result.update(dfs(i, g, n))
print(len(result))
for v in sorted(result):
print(v)
return
solution()
"""
걸린 시간: 몰라
시간 복잡도: 한 노드당 한번씩만 방문하기 때문에 O(n)이다.
해설: 그래프 모양에서 원형 부분에 해당하는 노드들이 답이라는 것은 금방 알았는데 구현이 어려웠다.
dfs로 구현했다.
본인 노드를 다시 만나게 되면 역으로 추적을 해야하기 때문에 record에 들어온 순서를 스택으로 기록하였고,
당연하게도 이미 result에 답으로 채택된 노드들은 dfs 탐색을 시작하지 않았다.
하지만 위 방식은 진출차수가 무조건 1이기 때문에 가능한 일이었다.
만약 진출차수가 1이상이었다면(제한이 없었다면) 본인 노드가 다시 만나서가 아닌, 여러 곳에서 온 것으로도 반복될 수 있기 때문이다.
이런 경우에는 dfs 재귀로 상태관리를 미방문(0), 보는중(1), 끝(2) 이렇게 3가지로 나눠서 진행해야한다.
"""

View File

@@ -0,0 +1,55 @@
# 녹색 옷 입은 애가 젤다지?
import sys
import heapq
input = sys.stdin.readline
def solution():
move = [(1,0), (-1, 0), (0, 1), (0, -1)]
num = 1
while 1:
n = int(input().rstrip())
if n == 0:
break
grid = [list(map(int, input().rstrip().split())) for _ in range(n)]
result = [[float('inf') for _ in range(n)] for _ in range(n)]
h = []
heapq.heappush(h, (grid[0][0], 0, 0))
result[0][0] = grid[0][0]
while h:
v, x, y = heapq.heappop(h)
for dx, dy in move:
now_x, now_y = x+dx, y+dy
if now_x >= n or now_x < 0 or now_y >= n or now_y < 0:
continue
now_v = result[x][y] + grid[now_x][now_y]
if now_v < result[now_x][now_y]:
result[now_x][now_y] = now_v
heapq.heappush(h, (now_v, now_x, now_y))
print(f"Problem {num}: {result[n-1][n-1]}")
num += 1
return
solution()
"""
걸린 시간: 25분
시간 복잡도: 모든 정점을 다 들렸다가 가는 경우에 n^2만큼 봐야하고, 정점 하나를 볼때마다 힙 정렬에 의해 logn만큼 걸린다.
따라서 전체 시간복잡도는 O(n^2logn)이다.
해설: dp로 하려고 했지만 어디서 시작해서 어느 방향으로 채워야 확정인지 나오질 않기 때문에 dp는 불가능 하다.
그렇다면 결국 (0,0)에서 (n-1,n-1)로 가는 최단거리를 구해야하고 그 와중에 가중치가 있기 때문에 다익스트라로 풀 수 있다.
기본 구현 방식대로 진행하되, 정점간의 연결을 상하좌우로 하면 된다.
내가 푼 방식에서 힙에서 꺼냈을 때 v가 result[x][y]보다 크다면 더 볼 필요 없고, (n-1,n-1)이 점이 꺼내졌다면 거기서 다른
곳으로 더 갈 필요는 없기 때문에 끝내는 로직을 추가하면 좀 더 빨라질 수 있다.
"""

View File

@@ -0,0 +1,49 @@
# 택배 배송
import sys
import heapq as h
input = sys.stdin.readline
def solution():
n, m = map(int, input().rstrip().split())
g = {i+1: [] for i in range(n)}
dk = [float('inf') for _ in range(n+1)]
for _ in range(m):
v, w, k = map(int, input().rstrip().split())
g[v].append((k, w))
g[w].append((k, v))
heap = []
h.heappush(heap, (0, 1))
dk[1] = 0
while heap:
now_dist, now_v = h.heappop(heap)
if now_dist > dk[now_v]:
continue
for nxt_k, nxt_v in g[now_v]:
new_dist = dk[now_v]+nxt_k
if new_dist < dk[nxt_v]:
dk[nxt_v] = min(dk[nxt_v], dk[now_v]+nxt_k)
h.heappush(heap, (new_dist, nxt_v))
print(dk[n])
return
solution()
"""
걸린 시간: 47분
시간 복잡도: 간선만큼 진행하는데 이때 heap 정렬이 매번 일어나기 때문에 O(mlogn)이다.
해설: 시작지점에서 끝지점까지의 가중치에 따른 최단거리를 구하는 것이기 때문에 다익스트라를 활용하면 된다.
현재 노드에서 다음 노드까지의 최단거리가 아니라 전체 다익스트라 결과에서 가장 최단거리인 노드로 다시 시작을 하는 것이었다.
이렇게 진행하면 visited를 쓰지 않아도, heap에 중복된 노드가 들어갈 수 있지만 거리가 먼 것은 배제하기 때문에 지장이 없다.
"""

View File

@@ -0,0 +1,42 @@
# 0 만들기
import sys
from collections import deque
input = sys.stdin.readline
OPERATOR = [" ", "+", "-"] # 아스키 순서
def dfs(depth, n, exp):
if depth == n:
exp += str(depth)
if eval(exp.replace(" ", "")) == 0:
print(exp)
return
for op in OPERATOR:
dfs(depth+1, n, exp+str(depth)+op)
def solution():
t = int(input().rstrip())
for _ in range(t):
n = int(input().rstrip())
dfs(1, n, "")
print()
return
solution()
"""
걸린 시간: 몰라
시간 복잡도: 전체 탐색이고, 연산자가 8칸에 3가지씩 가능하기 때문에 3^8이고, 테스트 케이스가 최대 9개이므로 (3^8)*9.
따라서 3^10이다. 60000정도이다.
해설: 전체를 다 해봐야지 알 수 있다는 것을 파악한 후 모든 경우의 수를 어떻게 확인할지가 관건이었다.
중첩 for문을 하려고 했으나 중첩이 너무 많아져서 dfs로 완전 탐색을 하기로 했다.
문자열 수식을 계산하는 것을 eval()을 몰라서 split하고 어떻게 해서 계산하려고 했는데 그냥 eval() 썼다.
"""

View File

@@ -0,0 +1,94 @@
# 문자열 폭발
import sys
input = sys.stdin.readline
def solution():
s = input().rstrip()
stack = []
result = ""
bomb = input().rstrip()
for ch in s:
stack.append(ch)
if stack[-1] == bomb[-1]:
idx = 0
while len(stack) >= len(bomb) and idx < len(bomb):
if stack[-1-idx] != bomb[-1-idx]:
result += "".join(stack)
stack = []
break
idx += 1
if idx == len(bomb):
for _ in range(len(bomb)):
stack.pop()
result += "".join(stack)
if not result:
print("FRULA")
else:
print(result)
return
solution()
"""
걸린 시간: 34분
시간 복잡도: 한 문자당 상수번 다뤄지기 때문에 O(n)이다.
해설: 처음에는 bomb 문자열의 순서와 쌍을 기록하는 방식으로 연결관계를 계속 파악하려고 했다. 그렇다보니 조건 분기가 많아졌고,
틀렸을 때는 어디가 잘못됐을지 감도 안 잡혀서 과감하게 다른 풀이로 전향했다.
좀 더 생각을 해보니 문자를 볼때 bomb의 마지막 문자일때만 이미 들어갔던 것들을 쭉 보고 만약에 bomb이 아니라면
지금까지 본 문자열은 앞으로도 절대 폭발할 수 없다는 것을 깨달았다.
따라서 방금 과정을 진행한 후 폭발하지 않았다면 stack에 있는 모든 문자를 다시는 보지 않기 위해 stack에서 빼고 문자열로 바꿔놓는다.
만약 폭발했다면 stack에 남아있는 것들이 또 연결되어 폭발할 수 있으므로 남겨놓는다.
"""
# def solution(): # 12:25~ 1:15 실패
# s = input().rstrip()
# bomb = {ch: i+1 for i, ch in enumerate(input().rstrip())} # start 1~
# # print(bomb)
# dq = deque([])
# group = 0
# no_group = -1
# for i, ch in enumerate(s):
# # b = -1
# bomb_order = bomb.get(ch, -1)
# if bomb_order == 1:
# group += 1
# dq.append((bomb_order, group, i))
# # b = 1
# elif not dq:
# dq.append((bomb_order, no_group, i))
# # b = 2
# elif bomb_order == len(bomb) and dq[-1][0] == len(bomb)-1:
# now_group = dq[-1][1]
# while dq and dq[-1][1] == now_group:
# dq.pop()
# # b = 3
# elif bomb_order == dq[-1][0]+1:
# dq.append((bomb_order, dq[-1][1], i))
# # b = 4
# else:
# dq.append((bomb_order, no_group, i))
# # b = 5
# # print(b, dq)
# result = ""
# while dq:
# result += s[dq.popleft()[2]]
# if not result:
# print("FRULA")
# else:
# print(result)
# return
# solution()

View File

@@ -0,0 +1,46 @@
# 줄세우기
import sys
input = sys.stdin.readline
def test(students):
line = [float('inf')] * 20
count = 0
for i, s in enumerate(students):
now_idx = 0
while 1:
if line[now_idx] > s:
break
now_idx += 1
for j in range(i-1, now_idx-1, -1):
line[j+1] = line[j]
count += 1
line[now_idx] = s
return count
def solution():
p = int(input().rstrip())
for _ in range(p):
test_case = list(map(int, input().rstrip().split()))
print(test_case[0], test(test_case[1:]))
return
solution()
"""
걸린 시간: 33분
시간 복잡도: 새로운 학생이 line에 들어올때마다 원래 있던 모든 학생이 움직이는 경우가 최대이므로
학생 수를 n이라고 하면 학생 당 (n-1), (n-2),... 0 이므로 O(n^2)인데, 이 문제는 20명 고정이므로
19*20/2 = 190 이다.
해설: 기본 line을 float('inf')로 세팅함으로써 사람이 없는 경우는 맨 뒤에 두고 없는 사람 취급을 한다.
세팅 후 순서대로 한명씩 line에 넣는데 이때 나보다 큰 가장 앞에 있는 사람을 찾아야 하기 때문에
그냥 맨 처음부터 보다가 나보다 큰 사람이 있으면 그 index에 내가 서고, 뒤에 있는 사람들을
한 칸씩 뒤로 밀면 된다. 뒤로 밀때는 지금까지 line에 있는 사람 수에서 내가 들어가야할 index까지
거꾸로 오면서 한 칸씩 뒤로 밀면 된다. 이때 count+1을 해주고 결과값 count를 반환하고 출력.
"""

View File

@@ -0,0 +1,40 @@
# 한 줄로 서기
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
blank = [i for i in range(n)]
info = list(map(int, input().rstrip().split()))
result = [0] * n
for i in range(n):
result[blank[info[i]]] = i+1
blank.pop(info[i])
print(*result)
return
solution()
"""
걸린 시간: 25분
시간 복잡도: 원하는 것을 pop하는 것은 O(n)이 걸릴 수 있고, 그것을 n번 하므로 O(n^2)이다.
blank의 칸을 dict로 관리하면 O(n)으로 가능할 것 같다.
해설: 본인보다 키가 큰 사람이 왼쪽에 몇 명인지 안다는 것은 본인보다 모두가 키가 큰 구성에서는 몇 등인지 안다는 것이다.
그렇기 때문에 키가 1인 사람부터 등수를 구할 수 있고, 구한다음에는 1을 배제하고 2가 키가 제일 작은 구성에서 몇 등인지
구하는 방식으로 진행하면 된다.
이때, 이미 1이 차지한 등수를 제외하고 남은 칸들 중에 나온 등수대로 넣으면 된다.
따라서 빈 자리를 의미하는 blank 리스트에서 남는 칸들을 확인할 수 있고, info+1이 본인의 등수이지만 blank는 0부터 시작하므로
그냥 blank[info]를 하면 본인이 차지해야하는 칸이 나온다.
그 후 blank에서 차지한 칸을 지워주면 나오는 등수대로 잘 들어갈 수 있다.
"""

View File

@@ -0,0 +1,62 @@
# 주식
import sys
from collections import deque
input = sys.stdin.readline
def calculate_revenue(n, prices):
total = 0
q = deque([prices[0]])
up = 0
if prices[0] <= prices[1]:
up = 1
else:
up = 0
for i in range(1, n):
if len(q) != 0 and i == n-1 and prices[i] >= q[-1]:
high = q.pop() if i != n-1 else prices[i]
while len(q) >= 1 and q[0] > high:
q.popleft()
while len(q) >= 1:
total += (high-q.pop())
if not up:
if len(q) != 0 and prices[i] > q[-1]:
up = 1
q.append(prices[i])
continue
if len(q) != 0 and (prices[i] < q[-1] or i == n-1):
high = q.pop() if i != n-1 else prices[i]
while len(q) >= 1 and q[0] > high:
q.popleft()
while len(q) >= 1:
total += (high-q.pop())
up = 0
q.append(prices[i])
return total
def solution():
t = int(input().rstrip())
for _ in range(t):
n = int(input().rstrip())
prices = list(map(int, input().rstrip().split()))
print(calculate_revenue(n, prices))
return
solution()
"""
걸린 시간:
시간 복잡도:
해설:
"""

View File

@@ -0,0 +1,62 @@
# 등수 구하기
import sys
input = sys.stdin.readline
def binary_search(lst, v):
start, end = 0, len(lst)
while start < end:
mid = (start+end) // 2
if lst[mid] <= v:
end = mid
else:
start = mid+1
return start
def solution():
n, new_score, p = map(int, input().rstrip().split())
if n == 0:
print(1)
return
scores = list(map(int, input().rstrip().split()))
if n == p and new_score <= scores[-1]:
print(-1)
return
result = binary_search(scores, new_score)
print(result+1)
return
solution()
"""
걸린 시간: 1시간 (이분탐색 여러 종류를 공부하면서 하느라 좀 걸렸다..)
시간 복잡도: O(logn)
해설: 이분탐색으로 들어갈 곳을 정하고, 이때, 값이 같다면 맨 앞에 놓을 수 있도록 처음으로 lst[mid] > target이 되는 곳을 찾으면 된다.
이게 이분탐색 lower_bound이다. 여기서 찾고자 하는 것과 배열 상태에 따라 조건을 잘 설정해야되는데, 우리가 원하는 것은 3번이다.
오름차순이라면 x 이상, 초과를 찾을 것이고, 내림차순이라면 x 이하, 미만을 찾는 것이 일반화하는데 편하다.
1. 오름차순 배열에서 처음 x 이상을 찾는다면 lst[mid] >= x 이고, mid도 답이 될 수 있고, 왼쪽도 확인해봐야 하기 때문에 r = mid가 된다.
2. 오름차순 배열에서 처음 x 초과를 찾는다면 lst[mid] > x 이고, mid가 답이 될 수 있고, 왼쪽도 확인해봐야 하기 때문에 r = mid가 된다.
3. 내림차순 배열에서 처음 x 이하를 찾는다면 lst[mid] <= x 이고, mid가 답이 될 수 있고, 왼쪽도 확인해봐야 하기 때문에 r = mid가 된다.
4. 내림차순 배열에서 처음 x 미만을 찾는다면 lst[mid] < x 이고, mid가 답이 될 수 있고, 왼쪽도 확인해봐야 하기 때문에 r = mid가 된다.
아무튼 가장 중요한 것은 현재 mid가 답이 될 수 있는가에 대한 여부와 왼쪽과 오른쪽 중 어디가 답 가능성이 있는 방향인지를 보면
1. if 조건과 r, l의 매칭 & 2. r, l에 mid +1을 할지 말지를 정할 수 있다.
그 외에 이 문제는 경계에 대한 것이었기 때문에 l < r 조건에 [l, r) 구간이었는데,
원소를 특정하는 것은 mid를 보고 아니면 버리기 때문에 mid +-1은 항상 해주고, 구간도 [l, r]에 조건도 l <= r이다.
"""

View File

@@ -0,0 +1,64 @@
# 스위치 켜고 끄기
import sys
input = sys.stdin.readline
def work_mode1(s, s_state, s_num):
mul = 1
idx = s_num * mul
while idx <= s:
s_state[idx] = 1 - s_state[idx]
mul += 1
idx = s_num * mul
return
def work_mode2(s, s_state, s_num):
gap = 1
s_state[s_num] = 1 - s_state[s_num]
while s_num + gap <= s and s_num - gap >= 1:
if s_state[s_num + gap] != s_state[s_num - gap]:
break
s_state[s_num + gap] = 1 - s_state[s_num + gap]
s_state[s_num - gap] = 1 - s_state[s_num - gap]
gap += 1
return
WORK = {
1: work_mode1,
2: work_mode2
}
def solution():
s = int(input().rstrip())
s_state = [-1] + list(map(int, input().rstrip().split()))
n = int(input().rstrip())
for _ in range(n):
mode, s_num = map(int, input().rstrip().split())
now_work = WORK[mode]
now_work(s, s_state, s_num)
s_state = s_state[1:]
for i in range(s//20+1):
start, end = i*20, (i*20)+20
print(*s_state[start:end])
return
solution()
"""
걸린 시간: 16분
시간 복잡도: 남자, 여자 둘 다 한 턴에 스위치 전체 길이인 s에 대해 O(s)이므로 전체 시간 복잡도는 학생 수가 n일 때, O(s*n) 이다.
해설: 남자(mode1), 여자(mode2)로 작업을 나눠서 배수는 1,2,3 올라가면서 시작 버튼에 곱해주고, 옆으로 확인은 gap을 +1 해주면서
확인한다. while 반복문의 기본 규칙은 1과 s 사이의 범위이고, mode2에서 추가 escape는 보고 있는 두 스위치의 상태가 다를 때이다.
메인 함수에서는 mode에 맞게 work 함수를 할당 받은 후 작업을 진행하고, 20단위로 끊어서 출력한다.
추가로 worker 클래스를 만들어서 상속 받아서 하면 좀 더 안전한 코드가 될 것 같다.
"""

View File

@@ -0,0 +1,72 @@
# DFS와 BFS
import sys
from collections import deque
input = sys.stdin.readline
def dfs(n, g, v):
result = []
visited = [0]*(n+1)
stack = [v]
while stack:
w = stack.pop()
if visited[w]:
continue
result.append(w)
visited[w] = 1
for i in range(len(g[w])-1, -1, -1):
stack.append(g[w][i])
return result
def bfs(n, g, v):
result = []
visited = [0]*(n+1)
q = deque([v])
visited[v] = 1
while q:
w = q.popleft()
result.append(w)
for k in g[w]:
if not visited[k]:
q.append(k)
visited[k] = 1
return result
def solution():
n, m, v = map(int, input().rstrip().split())
g = [[] for _ in range(n+1)]
for _ in range(m):
v1, v2 = map(int, input().rstrip().split())
g[v1].append(v2)
g[v2].append(v1)
for i in range(1, n+1):
g[i].sort()
print(*dfs(n, g, v))
print(*bfs(n, g, v))
return
solution()
"""
걸린 시간: 34분
시간 복잡도: 간선 수에 따라 for문을 돌아보는 차이가 난다. 또한 어디 노드에 연결이 얼마나 되어 있느냐에 따라 sort의 차이도 난다.
해설: dfs는 시작 노드를 stack에 넣고 pop해가면서 인접 노드를 넣는 방식으로 구현이 가능하다.
이때, visited를 pop하고 찍음으로써 이전에 인접한 노드로 나왔더라도 한 번 더 나왔을 때 다시 넣을 수 있게 한다.
이는 깊이 탐색을 위해 뒤에서 나올수록 우선순위가 높아짐을 의미한다.
bfs는 시작 노드를 queue에 넣고 popleft 해가면서 인접 노드를 넣는 방식으로 구현이 가능하다.
이때, visited를 queue에 append 하면서 찍음으로써 이후에 인접한 노드로 또 나왔을 경우 다시 못 들어가게 한다.
이는 너비 탐색을 위해 먼저 나왔을수록 우선순위가 높음을 의미한다.
"""

View File

@@ -0,0 +1,61 @@
# 단축키 지정
import sys
input = sys.stdin.readline
def check_word_first(words, shortcut):
for i in range(len(words)):
c = words[i][0].upper()
if not shortcut.get(c, 0):
shortcut[c] = 1
return i
return -1
def check_word(word, shortcut):
for i in range(len(word)):
c = word[i].upper()
if not shortcut.get(c, 0):
shortcut[c] = 1
return i
return -1
def solution():
n = int(input().rstrip())
shortcut = {}
for _ in range(n):
option = input().rstrip().split()
shortcut_idx = check_word_first(option, shortcut)
if shortcut_idx != -1:
option[shortcut_idx] = f"[{option[shortcut_idx][0]}]" + option[shortcut_idx][1:]
print(*option)
continue
idx = 0
while idx < len(option):
shortcut_idx = check_word(option[idx], shortcut)
if shortcut_idx != -1:
option[idx] = option[idx][:shortcut_idx] + f"[{option[idx][shortcut_idx]}]" + option[idx][shortcut_idx+1:]
break
idx += 1
print(*option)
return
solution()
"""
걸린 시간: 41분
시간 복잡도: 완전 탐색이기 때문에 O(5*10*n)이다.
해설: 30개의 옵션에 1개 옵션 당 5개 이하 단어와 1개의 단어에 10개 이하의 알파벳이면 다 돌아봤자 1500이다.
따라서 조건대로 구현하면 된다. 대소문자를 구분하지 않기 때문에 다 upper로 만들어서 현재 사용된 옵션을 기록한다.
단어 첫 글자들을 보며 옵션 설정하고, 한 단어씩 쭉 보면서 옵션 설정을 한다.
옵션을 설정하면 출력 형태를 맞춰서 리스트에 저장하고 마지막에 한번에 출력한다.
"""

View File

@@ -0,0 +1,35 @@
# 주유소
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
dist = list(map(int, input().rstrip().split()))
price = list(map(int, input().rstrip().split()))
least_price = float('inf')
result = 0
for i in range(n-1):
if price[i] < least_price:
least_price = price[i]
result += (dist[i]*least_price)
print(result)
return
solution()
"""
걸린 시간: 12분
시간 복잡도: dist 길이만큼 한 번 돌기 때문에 전체 시간복잡도는 O(n)이다.
해설: 현재 지역 다음에 본인보다 더 싼게 있으면 거기까지만 가고 그 다음부터는 싼 곳에서 기름을 사야한다.
가장 쌌던 가격을 계속 기록해가면서 price를 마지막-1 까지 순회하면 끝.
"""

View File

@@ -0,0 +1,77 @@
# 에디터
import sys
from collections import deque
input = sys.stdin.readline
def l(left_q, right_q):
if len(left_q) >= 1:
right_q.appendleft(left_q.pop())
return
def d(left_q, right_q):
if len(right_q) >= 1:
left_q.append(right_q.popleft())
return
def b(left_q):
if len(left_q) >= 1:
left_q.pop()
return
def p(left_q, w):
left_q.append(w)
return
COMMANDS = {
"L": l,
"D": d,
"B": b,
"P": p,
}
def excute(left_q, right_q, *c):
command = COMMANDS.get(c[0], None)
if c[0] == "P":
command(left_q, c[1])
elif c[0] == "L" or c[0] == "D":
command(left_q, right_q)
elif c[0] == "B":
command(left_q)
else:
return
return
def solution():
s = input().rstrip()
m = int(input().rstrip())
left_q = deque(list(s))
right_q = deque([])
for _ in range(m):
excute(left_q, right_q, *(input().rstrip().split()))
result = list(left_q) + list(right_q)
print("".join(result))
return
solution()
"""
걸린 시간: 30분
시간 복잡도: deque는 pop이나 append가 어디든 O(1)이므로 명령어 수행은 항상 O(1)이고, m번 명령을 반복하기 때문에 O(m)이다.
그 후 마지막에 두 deque를 합쳐서 출력하기 때문에 n개의 단어에서 m만큼 글자가 추가 되었다면 O(n+m)이다.
따라서 전체 시간복잡도는 O(n+m)이다.
해설: 커서를 기준으로 양쪽에 deque를 배치한 후 l, d로 커서를 움직이면 양쪽 deque에서 요소들을 옮기고, b, p로 요소를 지우거나 추가하면
커서 기준 왼쪽으로 동작한다고 했으므로 왼쪽 deque로 요소를 조정한다. 마지막에 두 deque를 list로 만들어서 합치고 출력하면 끝
확장성을 위해 dispatch 전략을 사용했지만 l,d,b,p의 인자가 달라서 excute 함수에서 또 분기를 했다. -> l,d,b,p의 인자를 같게 하면 됨
자료구조가 deque에서 다른 것으로 바뀐다면 다 수정해야 하기 때문에 결합도가 너무 높다.
-> 추상화 계층 하나를 추가해서 Editor라는 클래스를 만들고 거기다가 기능들 쓴 다음에 l,d,b,p가 그걸 받아오면 나중에 Editor 클래스만 바꾸면 됨
"""

View File

@@ -0,0 +1,40 @@
# 지름길
import sys
input = sys.stdin.readline
def solution():
n, d = map(int, input().rstrip().split())
shortcut = {}
for _ in range(n):
start, end, cost = map(int, input().rstrip().split())
if end > d:
continue
if not shortcut.get(end, []):
shortcut[end] = []
shortcut[end].append((start, cost))
dp = [0] * (d+1)
for i in range(1, d+1):
shortcut_list = shortcut.get(i, [])
dp[i] = min(min([dp[s]+c for s, c in shortcut_list]) if shortcut_list else float('inf'), dp[i-1]+1)
print(dp[d])
return
solution()
"""
걸린 시간: 30분
시간 복잡도: 모든 지점에 대해 dp 값을 구하는데, 이때 한 지점은 연결된 지름길만큼 반복한다.
하지만 지름길 하나 당 한번만 보기 때문에 전체 시간복잡도는 O(n+d)이다.
해설: 지름길의 도착 위치 기준으로 그냥 가는 것과 여러 지름길 중 하나를 선택하는 것과 같이
여러 개를 비교해야했다. 그리고 비교를 하는 것이 과거의 지점에서 계산을 하는 것이기 때문에 dp를 생각했다.
현재 지점의 dp를 계산할 때 이 곳에 올 수 있는 모든 경우의 수 중 cost가 최소인 것을 구하면 된다.
따라서 지름길을 통해 오는 것과 바로 한 칸 전 위치에서 +1로 오는 경우 중 구하면 된다.
"""

View File

@@ -0,0 +1,74 @@
# 쉬운 최단거리
import sys
from collections import deque
input = sys.stdin.readline
def find_target(grid, n, m):
for i in range(n):
for j in range(m):
if grid[i][j] == 2:
target = (i, j)
return target
return (-1, -1)
def set_zero_land(grid, visited, n, m):
for i in range(n):
for j in range(m):
if grid[i][j] == 0:
visited[i][j] = 0
return
def bfs(grid, target, n, m):
dr = [1, -1, 0, 0]
dc = [0, 0, 1, -1]
visited = [[-1]*m for _ in range(n)]
set_zero_land(grid, visited, n, m)
q = deque([target])
visited[target[0]][target[1]] = 0
while q:
r, c = q.popleft()
for i in range(4):
now_r, now_c = r+dr[i], c+dc[i]
if now_r == target[0] and now_c == target[1]:
continue
if 0 <= now_r < n and 0 <= now_c < m and grid[now_r][now_c] != 0 and visited[now_r][now_c] == -1:
q.append((now_r, now_c))
visited[now_r][now_c] = visited[r][c] + 1
return visited
def solution():
n, m = map(int, input().rstrip().split())
grid = [list(map(int, input().rstrip().split())) for _ in range(n)]
target = find_target(grid, n, m)
result = bfs(grid, target, n, m)
for i in range(n):
print(*result[i])
return
solution()
"""
걸린 시간: 55분
시간 복잡도: target 찾고, 0 세팅 하는데 O(nm)이고,
bfs는 한 노드마다 4번의 인접 노드를 확인하므로 O(4nm)
전체 시간복잡도는 O(nm)이다.
해설: 각 점에서 도착지까지 계속 찾아가는건 말이 안되고, 인접 점의 결과에 +1을 하는 식으로
O(1)에 찾도록 생각을 하였다. dp가 떠올랐지만,
target 지점을 기준으로 +1씩 해야하기 때문에 target이 중간 어딘가에 있으면 dp 테이블을 채우기 애매했다.
따라서 너비우선 탐색을 떠올렸고, bfs로 진행했다.
조건들이 조금 귀찮아서 몇 번 틀렸는데, 0인 땅은 그냥 0이고, 1인데 못 가는 땅은 -1로 출력을 해야했다.
따라서 bfs 세팅에서 기본 visited를 -1로 잡고, 0인 땅은 0으로 초기화 해주는 작업을 했다.
"""

View File

@@ -0,0 +1,69 @@
# 수 이어 쓰기
import sys
input = sys.stdin.readline
def check_parttern(now, target):
idx = 0
len_target = len(target)
for c1 in now:
while idx < len_target:
if c1 == target[idx]:
idx += 1
break
else:
idx += 1
else:
return False
else:
return True
def solution():
s = input().rstrip()
len_s = len(s)
i = 0
target = 0
while i < len_s:
target += 1
j = i
while j < len_s and int(s[i:j+1]) <= target:
now = s[i:j+1]
if check_parttern(now, str(target)):
j += 1
else:
break
i = j
print(target)
return
solution()
"""
걸린 시간: 53분
시간 복잡도: s의 길이가 3000까지가 최대라고 했기 때문에 최악의 경우 0이 3000개 있으면
target이 30000까지 간다. 즉, len(s) = n, max(target) = 10n = T이다.
근데 s의 한 숫자는 한번씩만 보는데 내 코드는 한번 볼때 다음것이 패턴이
가능하다면 중복해서 보기 때문에 O(j^2)이긴 하지만 이렇게 한번하면 그만큼 가장 최근의 것은 1번 본다.
따라서 전체 시간복잡도는 O((n+T)^2) 정도이다.
근데 s의 i번째 수를 보는 것의 중복을 없애면 O(TlogT)가 될 수 있을 것 같다.(log 밑은 10)
해설: 처음엔 문제 이해가 좀 어려웠는데 써가면서 하니까 이해가 됐다.
그리고 주어진 테스트 케이스가 엄청 복잡한 것을 보면 규칙 찾아서 하는 것이라는 걸 의심해보자.
1~10까지를 한 세트라고 했을 때 지금 보는 숫자보다 작거나 같은 숫자가 나오면 한 세트가 넘어간 것이다.
이걸로 규칙 세워서 가려고 했는데 다음 세트로 넘어갈수록 숫자가 10의 자리나 100의 자리가 생기고, 바뀌고
그러기 때문에 쉽지 않았다. -> 주어진 숫자를 쭉 보면서 답이 될 수 있는 숫자를 하나씩 키워나가자.
s의 숫자들을 하나씩 보면서 target이 될 수 있는 숫자라면, 즉 target의 substring이라면 s의 다음 숫자를 보고
target도 하나 키우는 방식으로 진행했다. 아니라면 당연히 target만 하나 키우기.
여기서 substring 구하는 방식을 s의 i번째를 확인했음에도 안쪽 while문으로 i~i+j번째까지 계속 중복 확인을 했다.
이 부분을 따로 함수로 빼서 고치든가 했어야 했다.(어차피 그래도 답은 틀림)
아무튼 나는 now in target 방법으로 진행했는데, 반례가 존재했다. s의 11이 target의 101을 대표하지 못했다.
in으로는 0이 있어서 안되기 때문이다.
따라서 target의 한 글자와 s의 한글자씩 보는 방식으로 진행했다.
역시 i~i+j번째를 보기까지 중복이 있긴 해서 이 부분을 고쳐야 하긴 하지만 아무튼 통과했다.
"""

View File

@@ -0,0 +1,32 @@
# 문자열 교환
import sys
input = sys.stdin.readline
def solution():
s = input().rstrip()
k = s.count("a")
k_count = {"a": s[:k].count("a"), "b": s[:k].count("b")}
circle = s + s
result = k_count["b"]
for i in range(1, len(circle)-k):
k_count[circle[i-1]] -= 1
k_count[circle[i+k-1]] += 1
result = min(result, k_count["b"])
print(result)
return
solution()
"""
걸린 시간: 못 품
시간 복잡도: 전체에서 a의 개수(k)를 세고, k구간씩 옮겨가며 b개수를 슬라이딩 윈도우로 업데이트하기 때문에 전체 시간복잡도는 O(n)이다.
해설: 원형이라고 했기 때문에 같은 것 2배를 해준 문자열을 만든다. 여기서 기존 a가 다 연속이면 되기 때문에
a의 개수 k를 구해서 원형에서 k구간씩 확인하며 b를 다 빼주면 되는데, 이때 b개수의 최소값이 답이다.
"""

View File

@@ -0,0 +1,44 @@
# 숨바꼭질
import sys
from collections import deque
input = sys.stdin.readline
def solution():
n, k = map(int, input().rstrip().split())
max_len = 100000
line = [0 for _ in range(max_len+1)]
move = [-1, 1]
q = deque([n])
line[n] = 1
while q:
now = q.popleft()
for m in move:
now_move = now + m
if 0 <= now_move < max_len+1 and line[now_move] == 0:
q.append(now_move)
line[now_move] = line[now] + 1
if 0 <= now*2 < max_len+1 and line[now*2] == 0:
q.append(now*2)
line[now*2] = line[now] + 1
print(line[k]-1)
return
solution()
"""
걸린 시간: 몰라
시간 복잡도: 한 지점 당 큐에 한 번씩만 들어갔다가 나오기 때문에 전체 지점만큼의 시간이 걸린다. 따라서 O(100000)이다.
해설: 특정 위치를 볼 때 그 위치로부터 //2, -1, +1 지점에서 +1 한 것들과 기존 지점까지 총 4개 값의 최소값을 가져오면서 dp 테이블을
갱신하면 될 것 같았는데, 이렇게 되면 dp 테이블을 채우는 방향에 따라 비교해야하는 4지점이 다 안 채워진 경우가 있다.
따라서 bfs로 한번 이동하는 것을 하나의 계층으로 생각해서 진행하면 된다.
"""

View File

@@ -0,0 +1,36 @@
# 어두운 굴다리
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
m = int(input().rstrip())
x = list(map(int, input().rstrip().split()))
result = 0
for i in range(1, m):
now = (x[i]-x[i-1])//2 if (x[i]-x[i-1])%2==0 else (x[i]-x[i-1])//2 + 1
result = max(result, now)
result = max(result, x[0]-0, n-x[-1])
print(result)
return
solution()
"""
걸린 시간: 26분
시간 복잡도: 0에서 첫 번째 위치를 빼는 것이랑, 끝에서 마지막 위치를 빼는 것을 포함하여 약 m번의 연산을 진행한다.
m이 n보다 작으므로 전체 시간복잡도는 O(n)이다.
해설: 가로등이 놓일 위치 사이의 거리가 가장 큰 값을 2로 나눈 값이 답이다. 반씩 나눠서 그 구간을 커버해야하기 때문이다.
x를 순차적으로 가면서 앞의 위치와 차이//2 중 최대를 찾으면 되는데, 이때, 차이가 홀수인 경우 +1을 해줘야 커버가된다.
삼항연산자로 구분했지만 ceil을 하거나 -(-n//2)를 하면 같은 효과를 볼 수 있다.
파이썬의 //는 무조건 음의 무한대로 내림하기 때문에 -5 // 2는 -2.5가 -3이 된다. => 올림 효과를 볼 수 있다.
마지막으로 0과 n에는 가로등 하나가 온전히 감당해야 하므로 마지막에 비교해주면 끝.
"""

View File

@@ -0,0 +1,99 @@
# 진우의 달 여행
import sys
input = sys.stdin.readline
def solution():
n, m = map(int, input().rstrip().split())
direction = [-1, 0, 1]
grid = [list(map(int, input().rstrip().split())) for _ in range(n)]
dp = [[[0]*3 for _ in range(m)] for _ in range(n)]
for i in range(m):
for j in range(len(direction)):
dp[0][i][j] = grid[0][i]
for i in range(1, n):
for j in range(m):
for k in range(len(direction)):
if j == 0:
dp[i][j][k] = min([dp[i-1][j+direction[h]][h] for h in range(len(direction)) if h != k and direction[h] != -1]) + grid[i][j]
elif j == m-1:
dp[i][j][k] = min([dp[i-1][j+direction[h]][h] for h in range(len(direction)) if h != k and direction[h] != 1]) + grid[i][j]
else:
dp[i][j][k] = min([dp[i-1][j+direction[h]][h] for h in range(len(direction)) if h != k]) + grid[i][j]
print(min([min(dp[n-1][j]) for j in range(m)]))
return
solution()
"""
풀이(2) -> dp
걸린 시간: 45분
시간 복잡도: n*m*3(방향)으로 된 3차원 dp를 채우는데, 방향을 채울 때 본인을 제외한 방향들의 최소를 구하기 때문에 *2를 해서 (n*m*3*2)이다.
해설: 다른 풀이에서 DP가 있다는 것을 알게되어서 두 번째 풀이를 진행했다.
i, j, k는 행, 열, 이전 방향이라고 할때, dp[i][j][k]를 구하면 i-1번째 행에서 direction[k]방향을 제외한 j열의 값들 중에 최소와
현재 행렬 값(grid[i][j])의 합이다.
즉 [i][j][0]은 direction[0]이 -1방향이므로 [i-1][j-1]에서 값을 가져오는데 [i-1][j-1][0]을 제외한 값들 중에 최소를 가져온다.
"""
"""
풀이(1) -> dfs
걸린 시간: 35분
시간 복잡도: 첫 행에서 갈 수 있는 경우의 수는 3이고 마지막 행을 제외한 행에서 경우의 수는 2이므로 3*2^(n-2) 이다.
이것을 열 개수만큼 반복해야 하므로 3*2^(n-2)*m 이다. 사실상 O(2^n * m)이라서 말이 안되는 시간복잡도이지만 조건때문에 그냥 했다.
해설: 2 <= n, m <= 6인걸 봐서는 모든 경우의 수를 확인해보라는 것 같다. (3 * 2^4) * 6 개이므로 얼마 안된다.
dfs의 기저를 depth가 n-1보다 클때, 즉 마지막 행에 도달했을 때로 잡았고, 지금까지 더해온 total에 마지막 grid 값을 더한 뒤
result 리스트에 넣고 마지막에 min을 구해서 출력하도록 하였다.
이전에 어디로 갔는지도 계속 가져가며 갈 수 있는 전체 방향에서 이전에 간 방향 빼고, 열 밖으로 나가는 것까지 빼면 된다.
total을 어떻게 계속 갱신할지 dfs에 선언하면 꼬일 것 같아서 고민했는데 그냥 인자로만 계속 넘기면 괜찮은 것 같다.
"""
# result = []
# def solution():
# n, m = map(int, input().rstrip().split())
# grid = [list(map(int, input().rstrip().split())) for _ in range(n)]
# dx = [-1, 0, 1]
# dy = [1, 1, 1]
# def dfs(depth, col, before, total):
# if depth >= n-1:
# total += grid[depth][col]
# result.append(total)
# return
# for i in range(3):
# if i == before or col+dx[i] >= m or col+dx[i] < 0:
# continue
# dfs(depth+dy[i], col+dx[i], i, total+grid[depth][col])
# return total
# for i in range(m):
# dfs(0, i, -1, 0)
# print(min(result))
# return
# solution()

View File

@@ -0,0 +1,51 @@
# 볼 모으기
import sys
input = sys.stdin.readline
def count_result(n, balls, left, right, ball_count):
left_count, right_count = 0, 0
left_idx, right_idx = 0, n-1
while left_idx <= n-1 and balls[left_idx] == left:
left_count += 1
left_idx += 1
while right_idx >= 0 and balls[right_idx] == right:
right_count += 1
right_idx -= 1
return min(ball_count[left]-left_count, ball_count[right]-right_count)
def solution():
n = int(input().rstrip())
balls = list(input().rstrip())
ball_count = {"R": 0, "B": 0}
for b in balls:
ball_count[b] += 1
result = min(count_result(n, balls, "R", "B", ball_count), count_result(n, balls, "B", "R", ball_count))
print(result)
return
solution()
"""
걸린 시간: 50분
시간 복잡도: count_result가 왼쪽, 오른쪽에 대해 첫 묶음이 몇 개인지 계산하는데 최악의 경우 전체이므로,
O(n)이다. 처음에 ball_count를 셀 때 O(n)이고, count_result가 2번이므로 O(3n), 전체 시간복잡도는 O(n)이다.
해설: 같은 것끼리 묶인 최종 상태는 한쪽 끝이 B, R 하나씩 있어야 된다.
따라서 큰 경우의 수는 양 끝이 R, B 이거나 B, R인 경우이다.
이제 각 경우의 수마다 R을 넘길지 B를 넘길지 계산하면 되는데,
정해진 경우의 끝에 따라서 같은 묶음만 뺀 개수를 세면 된다.
경우의 수 1) R, B일때 왼쪽 R 묶음 제거한 R 개수, 오른쪽 B 묶음 제거한 B개수 중 최소
경우의 수 2) B, R일때 왼쪽 B 묶음 제거한 B 개수, 오른쪽 R 묶음 제거한 R개수 중 최소
두 경우의 수 중 최소 값 구하면 된다.
"""

View File

@@ -0,0 +1,35 @@
# 최소 힙
import sys
import heapq
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
h = []
for i in range(n):
v = int(input().rstrip())
if v == 0:
if len(h) == 0:
print(0)
continue
print(heapq.heappop(h))
else:
heapq.heappush(h, v)
return
solution()
"""
걸린 시간: 5분
시간 복잡도: heappush와 heappop 모두 현재 트리의 높이 즉, logn만큼 든다.
트리 길이가 점점 길어져서 n까지 가는 것이기 때문에 평균적으로 O(logn)이다.
n번 진행하기 때문에 전체 시간복잡도는 O(nlogn)이다.
해설: 대놓고 힙 써서 구현하라고 해서 쉬웠다.
0일때 pop하는데, 배열 길이가 0이면 0 출력하도록 설정
넣을때는 heappush, 뺄때는 heappop 하면 끝
"""

View File

@@ -0,0 +1,58 @@
# IF문 좀 대신 써줘
import sys
input = sys.stdin.readline
def binary_search(target, badge):
l, r = 0, len(badge)
while l < r:
mid = (l+r)//2
if badge[mid][1] >= target:
r = mid
else:
l = mid + 1
return l
def solution():
n, m = map(int, input().rstrip().split())
badge = []
name, value = input().rstrip().split()
badge.append((name, int(value)))
for i in range(n-1):
name, value = input().rstrip().split()
if badge[-1][1] == int(value):
continue
badge.append((name, int(value)))
for i in range(m):
target = int(input().rstrip())
idx = binary_search(target, badge)
print(badge[idx][0])
return
solution()
"""
걸린 시간: 40분
시간 복잡도: m개의 값에 대해 n범위에서 이분탐색을 진행하기 때문에 O(mlogn)
해설: 문제가 너무 단순해서 뭔가 했는데 n, m <= 10^5 이기 때문에 n에서 비교를 최대한 줄이는 것이 핵심이다.
n으로 만들어진 수직선 상에 m이 들어갈 위치를 찾으면 되기 때문에 이분탐색으로 진행한다.
10^5는 17정도에서 끝낼 수 있다. badge에 상한 값이 겹치는 애들은 첫번째만 넣고, m개 받으면서 이분탐색 진행
이분탐색 정리했는데 자꾸 헷갈리니까 다시 정리
내가 찾고자 하는건 badge의 idx이다. 즉 mid 기준으로 생각하는게 편하다.
지금 문제는 target이 어느 상한에 속하나인데, 100이라면 100이상의 상한을 찾아서 가장 먼저 나오는
상한값이 답인 것이다. 따라서 badge[mid][1] >= target으로 기본을 잡으면 되고,
저게 성립하면 mid는 답이 될 수 있지만 왼쪽에 답이 있을 수도 있다.
따라서 r을 움직여야하고, 현재 mid를 포함한 값이 되야 한다. r = mid
반대 상황은 badge가 target보다 작은거니까 현재 mid가 답이 될 수 없으므로 l = mid + 1
원소의 유무 판단이 아닌 들어갈 곳 찾기기 때문에 l, r = 0, len(badge)로 한다.
while문 기본 조건은 들어갈 자리를 찾는 것이기 때문에 l = r일때는 알아보는 것이 의미 없다. 따라서 while l < r
끝.
"""

View File

@@ -0,0 +1,78 @@
# 햄버거 분배
import sys
from collections import deque
input = sys.stdin.readline
def clear_left(d, i, k):
while len(d) >= 1 and d[0][0] < i - (k):
d.popleft()
return
def clear_right(d, i, k):
while len(d) >= 1 and d[-1][0] < i - (k):
d.pop()
return
def manage_h(d, i, t, result):
if len(d) >= 1 and d[-1][1] == "P":
d.pop()
result += 1
else:
d.append((i, t))
return result
def manage_p(d, i, t, result):
if len(d) >= 1 and d[0][1] == "H":
d.popleft()
result += 1
else:
d.appendleft((i, t))
return result
MANAGEMENT = {
"H": manage_h,
"P": manage_p
}
def solution():
n, k = map(int, input().rstrip().split())
table = input().rstrip()
d = deque()
result = 0
for i, t in enumerate(table):
clear_left(d, i, k)
clear_right(d, i, k)
manager = MANAGEMENT.get(t, None)
if not manager:
return -1
result = manager(d, i, t, result)
print(result)
return
solution()
"""
걸린 시간: 35분
시간 복잡도: deque의 길이를 k로 유지하면서 테이블에 있는 하나의 요소는 deque에 1번만 들어오거나 나가기 때문에
한 요소 당 시간 복잡도는 O(1)이고 전체 요소에 대해서 진행하기 때문에 전체 시간복잡도는 O(n)이다.
해설: P, H가 짝을 지어서 사라지면 된다고 생각하여 스택을 떠올렸고, 오래된 H나 P는 선택할 수 없게 지워버리는 방법을 생각했을 때
큐가 생각나서, 합쳐서 deque를 사용하기로 하였다.
처음에는 오래된 것들을 나가는 방향을 popleft, P/H가 들어오는 곳은 append, 짝을 이뤄서 나가는 방향을 pop으로 생각했는데,
이러면 과거 H를 P가 먹을 수 있는 상황에서 가장 최신 H만 먹기 때문에 최선이 아니다.
따라서 P는 들어오는 것과 짝 맞춰서 나가는 것을 왼쪽, H는 오른쪽으로 함으로써 우선순위를 오래 기다린 사람과 오래된 햄버거로 하였다.
그리고 이번에 들어오는 요소는 k 범위 이전 것들과는 전혀 상호작용할 수 없으므로 매번 clear를 왼쪽, 오른쪽을 다 해준다.
참고: len(list, deque, set, dict)들 모두 원소의 개수를 따로 저장하고 있기 때문에 O(1)이다.
"""

View File

@@ -0,0 +1,47 @@
# 랭킹전 대기열
import sys
input = sys.stdin.readline
def solution():
p, m = map(int, input().rstrip().split())
room = {}
for i in range(p):
l, n = input().rstrip().split()
l = int(l)
for j in room:
if len(room[j]) < m and l >= j[0]-10 and l <= j[0]+10:
room[j].append((l, n))
break
else:
room[(l, n)] = [(l, n)]
for v in room.values():
if len(v) == m:
print("Started!")
else:
print("Waiting!")
for j in sorted(v, key=lambda x: x[1]):
print(*j)
return
solution()
"""
걸린 시간: 50분
시간 복잡도: p개의 플레이어를 방에 분배하기 위해 각 방을 확인해보는 시간 복잡도도 균등분배 기준으로 p//m개 방이므로, O(p* p//m) = O(p^2*m)이다.
room.values()를 확인하며 정렬하고 출력하는 것은 p//m 즉 균등분배 가정으로 나올 수 있는 최대 방 개수만큼 정렬하기 때문에
O(mlogm * p//m)이므로 O(plogm)이다.
따라서 전체 시간 복잡도는 O(p^2*m)이다.
p, m <= 300이기 때문에 그냥했지만 크기가 커진다면 모르기 때문에 들어갈 방을 범위로 나타내서 이분탐색을 하면 줄일 수 있다.
물론 방이 다 차면 같은 범위를 가지는 방이 또 필요할 수 있기 때문에 적절히 조치를 취해야한다.
해설: p개의 플레이어를 확인하면서 맞는 room이 있다면 넣고, 없다면 room을 만든다. 이때, 딕셔너리를 사용했는데 level만 키로 쓰면 중복될 수 있으므로,
(level, name)을 키로 써서 해야한다. 플레이어마다 생성된 room을 순서대로 다 범위를 확인한다. (이분 탐색을 쓰면 더 좋겠다.)
다 room에 넣은 후 room 길이에 따라 started와 waiting을 쓰고, 이름 순으로 정렬한 뒤 출력한다.
"""

View File

@@ -0,0 +1,72 @@
# 쿠키의 신체 측정
import sys
input = sys.stdin.readline
def find_heart(grid, n, k):
for i in range(n):
if grid[k][i] == "*":
return [k+1, i]
else:
return [-1, -1]
def down_check_star(grid, n, x, y):
length = 0
while x <= n-1:
if grid[x][y] != "*":
break
length += 1
x += 1
return length
def row_check_star(grid, n, x, y, dir): # 오른쪽 +1, 왼쪽 -1
length = 0
while y <= n-1 and y >= 0:
if grid[x][y] != "*":
break
length += 1
y += dir
return length
def solution():
n = int(input().rstrip())
grid = []
heart = [-1, -1]
left_arm = 0
right_arm = 0
mid = 0
left_leg = 0
right_leg = 0
for i in range(n):
grid.append(input().rstrip())
if heart[0] == -1:
heart = find_heart(grid, n, i)
left_arm = row_check_star(grid, n, heart[0], heart[1]-1, -1)
right_arm = row_check_star(grid, n, heart[0], heart[1]+1, +1)
mid = down_check_star(grid, n, heart[0]+1, heart[1])
left_leg = down_check_star(grid, n, heart[0]+mid+1, heart[1]-1)
right_leg = down_check_star(grid, n, heart[0]+mid+1, heart[1]+1)
heart[0] += 1
heart[1] += 1
print(*heart)
print(left_arm, right_arm, mid, left_leg, right_leg)
return
solution()
"""
걸린 시간: 30분
시간 복잡도: 팔, 다리, 허리는 일자로 있기 때문에 O(n)이지만, 머리는 (0,0) 부터 쭉 찾아서 처음 나오는
*이므로 O(n^2)이다. 따라서 전체 시간 복잡도는 O(n^2)이다.
해설: (0,0)에서 선형으로 찾다가 가장 먼저 나오는 *이 머리이다. 이 머리 바로 한 칸 밑이 심장이다.
심장에서 팔, 다리, 허리를 구하면 되는데, 연속된 *을 찾는 것이다.
이는 행으로 연속된 별 찾기와 열로 연속된 별 찾기 함수 2개를 만들어서 진행하면 된다.
위로 올라갈 일은 없어서 열은 그냥 down으로 했고, 행은 row로 방향을 인자로 받아서 체크했다.
"""

View File

@@ -0,0 +1,61 @@
# 타노스
import sys
from collections import deque
input = sys.stdin.readline
def solution():
s = input().rstrip()
s_dict = {"0": [], "1": []}
len_0 = 0
len_1 = 0
for i in range(len(s)):
if s[i] == "0":
s_dict["0"].append(i)
len_0 += 1
else:
s_dict["1"].append(i)
len_1 += 1
len_0 //= 2
len_1 //= 2
s_dict["0"] = deque(s_dict["0"][:len_0])
s_dict["1"] = deque(s_dict["1"][len_1:])
new_s = ""
for _ in range(len_0+len_1):
idx_0 = s_dict["0"][0] if len_0 > 0 else float('inf')
idx_1 = s_dict["1"][0] if len_1 > 0 else float('inf')
if idx_0 > idx_1:
s_dict["1"].popleft()
len_1 -= 1
new_s += "1"
else:
s_dict["0"].popleft()
len_0 -= 1
new_s += "0"
print(new_s)
return
solution()
"""
걸린 시간: 40분
시간 복잡도: s길이만큼 개수를 세고, s길이의 반만큼 재배열을 하기 때문에 O(len(s))이다. 그런데 나처럼 popleft 하면서 하지 않고,
그냥 두 리스트 합친다음에 sort하면 nlogn이므로 내가 더 낫다.
해설: 새로운 문자열 s는 기존 s에서 재배열 하는 것이 아니라 그냥 빼는 것이었다. 문제 설명 개별로네..
아무튼 그래서 개수를 세고 1은 앞에 있는 것부터 반만큼 지우고, 0은 뒤에 있는 것부터 반만큼 지우면 된다.
그렇게 하기 위해서 0과 1의 인덱스를 따로 기록해두고 0의 인덱스 리스트는 앞에서부터 반 자르고, 1은 반부터 뒤까지 자른다.
마지막으로 이 인덱스 리스트 2개를 맨 앞에 것들끼리 비교해서 작은 놈을 popleft 하고, 그에 맞는 숫자(0 또는 1)을 new_s에 추가한다.
deque는 슬라이스를 지원하지 않아서, 리스트에서 슬라이스하고 popleft 하기 전에 deque로 만들었다.
"""

View File

@@ -0,0 +1,55 @@
# N번째 큰 수
import sys
import heapq
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
rank = list(map(int, input().rstrip().split()))
heapq.heapify(rank)
for _ in range(n-1):
nums = list(map(int, input().rstrip().split()))
for i in range(n):
heapq.heappush(rank, nums[i])
heapq.heappop(rank)
print(heapq.heappop(rank))
return
solution()
"""
걸린 시간: 50분(메모리 때문에 고민 좀 했다.)
시간 복잡도: O(n^2logn) n^2개의 수를 읽을때마다 n개 길이의 힙에서 push와 pop을 하기 때문에 정렬 하는 것까지 logn해서 O(n^2logn)이다.
해설: 첫 번째 코드는 맨 마지막 행부터 n개중에 1등을 뽑고 1등이 나온 열에서 바로 한칸 위에 애를 데리고 또 n개중에 1등을 뽑고 총 n번을 진행하려고 했다.
그런데 결국 이게 O(n^2)이라는 것을 깨닫고 여기서 n개 중에 1등을 뽑는 방식을 줄이기 위해 heap을 생각해냈다. 그러면 1등 뽑을 때 O(logn)에 할 수 있다.
다른 문제가 하나 있는데 메모리 제한이 12MB라서 처음에 n^2개의 수들을 리스트에 저장하지 말고 한 줄씩 읽는대로 처리해야했다.
따라서 한 줄씩 heap에 넣으며 1등만 데리고 다음 줄로 넘어가서 확인하는 방식으로 하려고 했는데 이건 마지막 라인에서 경합 자격도 없는 애가 승부를
벌이게 되는 논리적 모순이 생기기 때문에 그냥 하나씩 계속 읽으면서 길이 n을 유지하는 최소 힙에 넣고 마지막에 heappop 하면 된다.
"""
# def solution():
# n = int(input().rstrip())
# nums = [[] for _ in range(n)]
# for _ in range(n):
# row = input().rstrip().split()
# for i in range(n):
# nums[i].append(int(row[i]))
# rank = [(-nums[i].pop(), i) for i in range(n)]
# heapq.heapify(rank)
# for i in range(n-1):
# _, nums_idx = heapq.heappop(rank)
# heapq.heappush(rank, (-nums[nums_idx].pop(), nums_idx))
# print(-heapq.heappop(rank)[0])
# return

View File

@@ -0,0 +1,59 @@
# 영단어 암기는 괴로워
import sys
import heapq
input = sys.stdin.readline
def set_priority(words, word):
word_info = words.get(word, None)
frequency, length = 0, 0
if word_info:
frequency = -(word_info[0]) + 1
length = -(word_info[1])
else:
frequency = 1
length = len(word)
words[word] = [-frequency, -length, word, 0]
return words[word]
def solution():
n, m = map(int, input().rstrip().split())
words = {}
word_list = []
for i in range(n):
word = input().rstrip()
if len(word) < m:
continue
heapq.heappush(word_list, set_priority(words, word))
for i in range(len(word_list)):
f, l, w, is_pop = heapq.heappop(word_list)
if words.get(w)[3]:
continue
else:
print(w)
words[w][3] = 1
return
solution()
"""
걸린 시간: 35분
시간 복잡도: set_priority는 단어 길이를 세고, 정보를 dict에서 읽고 쓰는 것인데 m은 최대 10이므로 전체 시간복잡도는 O(1)이다.
solution 함수에서 n개의 단어를 보며 set_priority를 실행하고, heap에 push하므로 요소당 O(logn)이기 때문에 O(nlogn)이다.
마지막으로 word_list를 확인할 때도 heap에서 pop이므로 O(nlogn)이다. 따라서 전체 시간복잡도는 O(nlogn)이다.
해설: heap에서 정렬할 때 우선순위 3가지로 정렬하도록 리스트를 넣는다.
그러기 위해 word_info dict을 만들어서 정보 업데이트를 O(1)에 한다.
단어마다 정보를 업데이트 해가며 heap에 넣고, 다 넣은 다음 heappop 하면서 기존에 안 나온 것들만 출력을 한다.
좀 빠르게 해보겠다고 heap 써서 O(nlogn)으로 했지만 그냥 Counter 하고, 우선순위 역순으로 sort 3번 하면 이것도 O(nlogn)이다...
"""

View File

@@ -0,0 +1,44 @@
# 겹치는 건 싫어
import sys
from collections import deque
input = sys.stdin.readline
def solution():
n, k = map(int, input().rstrip().split())
s = list(map(int, input().rstrip().split()))
idx = 0
c = {}
d = deque([])
result = 0
while idx < len(s):
new = s[idx]
if not c.get(new, 0):
c[new] = 0
d.append(new)
c[new] += 1
if c[new] > k:
while d and c[new] > k:
now = d.popleft()
c[now] -= 1
result = max(result, len(d))
idx += 1
print(result)
return
solution()
"""
걸린 시간: 15분
시간 복잡도: 한 요소가 deque에 들어갔다가 나오기를 한번씩만 하므로 O(n)이다.
해설: deque에 숫자를 넣고, 숫자마다 개수를 dict로 센다.
숫자를 deque에 넣었을 때 그 숫자의 개수가 k개를 넘으면 그 숫자가 k개 이하가 될때까지 deque에서 popleft를 한다.
매번 현재 길이를 최대 길이와 비교해간다.
"""

View File

@@ -0,0 +1,38 @@
# 카드2
import sys
from collections import deque
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
cards = deque([i for i in range(1, n+1)])
drop_count = 0
while drop_count < n-1:
cards.popleft()
drop_count += 1
cards.append(cards.popleft())
print(cards.pop())
return
solution()
"""
걸린 시간: 13분
시간 복잡도: deque에 넣고 빼는 작업은 O(1)이므로, 전체 시간복잡도는 O(n)이다.
해설: deque에 넣고 진행할 경우 앞, 뒤에 대한 추가, 삭제 연산이 O(1)이므로 이 자료구조를 활용해서 상황을 그대로 구현하면 된다.
규칙을 좀 찾아보면 먼저 홀수를 다 버리고, 짝수만 남은 상태에서 또 지워나가는 규칙을 찾을 수 있어서 수학적으로 풀 수 있을 것 같지만,
이 문제를 서비스 자체라고 생각하면 조건이 어떻게 바뀔지 모르는 상태에서 규칙을 찾아서 그 규칙에만 맞는 코드를 짜는 것보다는
시뮬레이션쪽으로 설계함으로써 사람이 하기 힘든 반복 작업을 자동화하는 것이 더 목적성이 맞다고 생각한다.
"""

View File

@@ -0,0 +1,46 @@
# 블로그
import sys
from collections import deque, Counter
input = sys.stdin.readline
def solution():
n, x = map(int, input().rstrip().split())
one_days = list(map(int, input().rstrip().split()))
duration = deque([one_days[i] for i in range(x)])
duration_result = [0] * n
duration_result[x-1] = sum(duration)
for i in range(x, n):
before = duration.popleft()
duration.append(one_days[i])
after = duration[-1]
duration_result[i] = duration_result[i-1] - before + after
c = Counter(duration_result)
max_vistor = max(c)
if max_vistor == 0:
print('SAD')
else:
print(max_vistor)
print(c.get(max_vistor))
return
solution()
"""
걸린 시간: 19분
시간 복잡도: 전체 요소를 한, 두바퀴 정도 돌기 때문에 O(n)이다.
해설: 그냥 인덱스로 앞 뒤로 줄이고, 늘이면 되는데 슬라이딩 윈도우는 deque를 쓰는게 좀 직관적이라서 deque를 사용했다.
최대값과 count를 계속 업데이트 하는 것이 코드가 가독성이 떨어지는 것 같아서 어차피 그렇게 연산하는 것과
마지막에 결과 리스트 한 바퀴 돌면서 개수 세는 연산이 비슷하기 때문에 그냥 결과 리스트에 쭉 저장하고 Counter로 개수를 셌다.
0이 최대일 경우 SAD 출력(처음에 이거 안해서 틀림), 아니면 최대값과 그 개수를 출력한다.
"""

View File

@@ -0,0 +1,38 @@
# 가희와 키워드
import sys
input = sys.stdin.readline
def solution():
n, m = map(int, input().rstrip().split())
memo = set()
for _ in range(n):
memo.add(input().rstrip())
for _ in range(m):
for keyword in input().rstrip().split(","):
if keyword in memo:
memo.remove(keyword)
print(len(memo))
return
solution()
"""
걸린 시간: 20분
시간 복잡도: 한 글에 최대 키워드가 10개이므로, set으로 차집합을 구하면 두 집합 중 작은 것만큼 시간이 드는데,
작은 것의 최대 길이가 10이므로 O(10m)이다. set의 길이는 key의 개수이므로 O(1)이기 때문이다.
해설: 처음에는 memo - keyswords를 했는데 이 연산은 비파괴 연산이라 기존 set들을 유지해야하므로
차집합된 set을 새로 만든다. 따라서 memo 길이만큼의 시간이 든다.
그래서 in으로 있는지 확인(O(1))하고, remove(O(1))로 지웠다.
remove는 key가 없으면 keyerror가 나는데 그래서 나는 있을 때만 지워서 괜찮았고,
discard를 쓰면 있으면 지우고 없으면 그냥 가만히 있는다고 한다.
"""

View File

@@ -0,0 +1,59 @@
# 창고 다각형
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
line = sorted([tuple(map(int, input().rstrip().split())) for _ in range(n)])
high_stack = []
result = 0
for x, y in line:
if not high_stack:
high_stack.append((x, y))
continue
last_pop = ()
while len(high_stack) != 0 and high_stack[-1][1] <= y:
last_pop = high_stack.pop()
if len(high_stack) == 0:
result += (x - last_pop[0])*last_pop[1]
high_stack.append((x, y))
for _ in range(len(high_stack)):
x, y = high_stack.pop()
if len(high_stack) == 0:
result += y
else:
result += (x - high_stack[-1][0])*y
print(result)
return
solution()
"""
걸린 시간: 40분
시간 복잡도: while문이 있지만 어차피 전체적으로는 요소 기준으로 봤을 때 stack에 들어가고 나오는거 한번씩이기 때문에 O(n)이다.
해설: 가장 높은 놈(top)을 만날때까지는 그냥 max 갱신해가며 하면 되지만 내리막에서는 작은 놈을 갱신하며 가야된다.
근데 내려갈때 무지성으로 작은 놈만 갱신해가면, 갑자기 큰 놈이 나왔을 때 우물형태가 생기므로 조건에 위배되기 때문에 지금까지 구했던것을 다시 계산해야된다.
근데 그러면 돌아가서 다시 계산하기 때문에 O(n^2)이 되버린다.
따라서 작은 놈을 계속 가져가되, 그것보다 큰 놈이 나왔을 때 이전 작은 것들을 버리는 방식으로 하면 된다. 이건 stack을 쓰면 쉽게 구현이 가능하다.
따라서 스택에 넣는 조건을 내가 들어갈때 스택에(이전 놈들) 나보다 작은 애들은 다 pop하고 들어간다. 그렇게 끝에는 top과 완벽한 내리막만 남는다.
이제 stack에서 pop을 하면서 pop한 녀석과 아직 stack에 남아있는 놈과의 거리로 넓이를 계산하고 마지막에 나오는 녀석은 너비가 1이므로 계산하면 된다.
한 가지 문제는 오르막에서도 이 조건을 그대로 쓰면 나보다 작은 이전 놈들의 권리가 없어진다.
따라서 내가 스택에 들어갈 때 나보다 작은 놈들을 다 pop했는데 stack이 비어 있다면 아직 top이 없었던 것이기 때문에 마지막에 나온 애는 넓이를 계산해준다.
다른 풀이로는 가장 높은 놈을 기준으로 오르막과 내리막이 나올 수밖에 없으므로, 왼쪽에서 top까지 max를 갱신해가며 넓이 구하고,
오른쪽에서 top까지 반대로 가면서 max 갱신해가며 넓이 구해서 더하면 끝이긴 하네.
"""

View File

@@ -0,0 +1,56 @@
# 예산
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
req = list(map(int, input().rstrip().split()))
m = int(input().rstrip())
if sum(req) <= m:
print(max(req))
return
now_ceil = m // n
now_asset = m
sorted_req = sorted(req)
idx = 0
while idx < n-1 and now_ceil >= sorted_req[idx]:
now_asset -= sorted_req[idx]
idx += 1
now_ceil = now_asset // (n-idx)
print(now_ceil)
return
solution()
"""
걸린 시간: 1시간 10분
시간 복잡도: 내 풀이는 정렬할 때 O(nlogn)이고, n개의 요청예산을 보기 때문에 O(n)이라서 전체 시간복잡도는 O(nlogn)이다.
이분탐색 풀이의 경우는 0부터 max(req) 범위에서 찾기 때문에 log(max(req))이고, 매 mid마다 req 내용을 다 확인하므로
O(nlog(max(req)))이다.
문제에서는 req <= 10^5이고, n <= 10000이기 때문에 log로 치면 내가 더 빠르지만 이 조건이 바뀌는 것에 따라 달라질 것 같다.
해설: 요청 예산의 합이 m보다 작거나 같다면 그냥 다 주고 요청 예산 중 최대값을 출력하면 된다.
근데 아니라면 두 가지 풀이 방법이 있다.
먼저 내가 처음 푼 방식은 요청 예산들을 오름차순으로 정렬한 후 처음 상한액을 평균값으로 매긴다.
그 다음 한 곳씩 예산을 나눠주는데 이때 상한액 이하의 곳들은 나눠주고 남은 예산으로 남은 곳의 수를 나눠서 다시 평균값을 상한액으로 바꾼다.
이 과정을 반복하다가 현재 상한액보다 많은 곳을 요청한 곳이 있으면 앞으로 뒤에 있는 곳들도 요청한만큼 채워줄 수 없기 때문에
이번 상한액이 답이다.
근데 이분탐색으로 푸는 방법이 있음.
전체 요청 예산의 범위 중 상한액을 이분 탐색으로 찾아가는 것임.
한 점을 찾는 것이기 때문에 l <= r 조건에서 [mid]를 설정하고, n개의 요청 예산을 설정한 mid 기준으로 다 분배해본 후,
m 이하 중 최대값을 찾으면 된다.
s를 그때그때 결과라고 하면, 오름차순 정렬에서 s가 m 이하인 경우를 찾는 것이기 때문에 s <= m 이면 답이 될 수 있고,
오른쪽을 찾아봐야하기 때문에 l = mid라고 생각할 수 있지만, 이것은 들어갈 곳이 아닌 한 점을 찾는 것이기 때문에
l <= r을 한 이상 안 끝날 수도 있다. 따라서 mid는 따로 기록해두고, l = mid + 1, r= mid - 1로 해가며 찾아야한다.
"""

View File

@@ -0,0 +1,40 @@
# 회전 초밥
import sys
from collections import Counter
input = sys.stdin.readline
def solution():
n, d, k, c = map(int, input().rstrip().split())
belt = [int(input().rstrip()) for _ in range(n)]
double_belt = belt + belt
eat = Counter(belt[:k])
eat.setdefault(c, 0)
eat[c] += 1
result = len(eat)
for i in range(1, len(double_belt)-k+1):
eat[double_belt[i-1]] -= 1
if eat[double_belt[i-1]] == 0:
del eat[double_belt[i-1]]
eat.setdefault(double_belt[i+k-1], 0)
eat[double_belt[i+k-1]] += 1
result = max(result, len(eat))
print(result)
return
solution()
"""
걸린 시간: 24분
시간 복잡도: 2n 길이의 리스트를 1번 순회하고, 그 과정에서 처리는 dict로 O(1)이므로, 전체 시간복잡도는 O(n)이다.
해설: 같은 두 리스트를 이어붙여서 원형을 만든 후 k 구간씩 슬라이딩하면서 다른 종류의 개수를 세면 된다.
각 음식마다 있는 개수는 dict에 저장하고, key의 개수 세기로 O(1)에 종류 개수를 세기 때문에 value가 0이면 del로 key까지 없앤다.
"""

View File

@@ -0,0 +1,29 @@
# 임스와 함께하는 미니게임
import sys
input = sys.stdin.readline
game_types = {"Y": 2, "F": 3, "O": 4}
def solution():
q = list(input().rstrip().split())
n, personnel = int(q[0]), game_types.get(q[1], 0)
people = set()
for _ in range(n):
people.add(input().rstrip())
result = len(people) // (personnel-1)
print(result)
return
solution()
"""
걸린 시간: 18분
시간 복잡도: 사람들 이름을 쭉 읽고, 쓰고, set() 개수 세는 것까지 n이므로 전체 시간복잡도는 O(n)
해설: 한 사람이 임스와 두 번 게임을 할 수는 없으므로 참여 명단을 중복을 제거하는 set()을 활용한다.
그 후 임스 본인을 제외한 게임에 필요한 인원 수로 set() 길이를 나누면 몫이 임스가 할 수 있는 게임 수이다.
"""

View File

@@ -0,0 +1,63 @@
# 비슷한 단어
import sys
from collections import Counter
input = sys.stdin.readline
def check_similar(now_word, target_c):
target_c_copy = target_c.copy()
now_word_left = []
for c in now_word:
if len(target_c_copy) < 1:
break
if target_c_copy.get(c, -1) == -1:
now_word_left.append(c)
continue
target_c_copy[c] -= 1
if target_c_copy[c] == 0:
del target_c_copy[c]
len_target_left = sum(target_c_copy.values())
result = 1 if len_target_left <= 1 and len(now_word_left) <= 1 else 0
return result
def solution():
n = int(input().rstrip())
result = 0
target = input().rstrip()
target_c = Counter(target)
for _ in range(n-1):
now_word = input().rstrip()
if len(now_word) >= len(target)+2:
continue
result += check_similar(now_word, target_c)
print(result)
return
solution()
"""
걸린 시간: 32분
시간 복잡도: 단어 길이를 m이라고 하자. 중복 제거할 때는 둘 중 짧은 단어만큼 진행된다. -> O(m)
target_c의 values 합 구하기 -> O(m)
이 두 과정이 check_similar()이고, O(m)이다. 이제 이 과정을 n-1개의 단어만큼 진행하기 때문에 전체 시간복잡도는 O(nm)이다.
해설: 비슷한 단어의 조건을 수치적으로 정리하면 target과 비교하는 단어의 길이 차이가 2이상 나면 안되고,
겹치는 글자들을 개수 기준으로 다 제거했을 때 양쪽 다 남은 글자가 1을 초과하면 안된다.
따라서 나는 target을 Counter로 만들고, 비교하는 단어를 쭉 보면서 target_c에 있으면 개수를 줄이는 방식으로 중복을 제거했다.
이때 target_c에 없으면 남는 글자는 따로 리스트에 보관하였다.
마지막으로 target_c의 values 합이 남는 글자 수고, len(lst)가 비교하는 글자의 남는 글자 수이므로 이것의 길이가 둘 다 <= 1일때만
비슷한 단어로 판정했다.
"""

View File

@@ -0,0 +1,63 @@
# KCPC
import sys
input = sys.stdin.readline
def write_board(board, i, j, s, idx):
if not board.get(i, 0):
board[i] = {"problems": {}, "submit_count": 0, "last_submit": -1}
board[i]["problems"][j] = max(board[i]["problems"].get(j, 0), s)
board[i]["submit_count"] += 1
board[i]["last_submit"] = idx
return
def check_our_rank(board, t):
ranking = []
for k in board:
info = (k, -sum(board[k]["problems"].values()), board[k]["submit_count"], board[k]["last_submit"])
ranking.append(info)
ranking.sort(key=lambda x: (x[1], x[2], x[3]))
for i, record in enumerate(ranking):
if record[0] == t:
return i+1
def test(n ,k ,t, m):
board = {} # 팀id: {문제: {번호: 점수}, 제출 횟수: ~, 마지막 제출 인덱스: ~}
for idx in range(m):
i, j, s = map(int, input().rstrip().split())
write_board(board, i, j, s, idx)
return check_our_rank(board, t)
def solution():
T = int(input().rstrip())
for _ in range(T):
n, k, t, m = map(int, input().rstrip().split())
print(test(n, k, t, m))
return
solution()
"""
걸린 시간: 40분
시간 복잡도: write_board가 O(1), check_our_rank가 sort때문에 O(nlogn)이므로, 이 둘을 활용한 test는 O(nlogn)이다.
따라서 전체 시간복잡도는 test하는 횟수를 곱해서 O(Tnlogn)이다.
해설: 문제에서 시키는대로 구현하면 된다. 순위를 정하기 위해 마지막에 정렬해야 하는데 기준이 되는 요소들이
최종 점수 > 제출 횟수 > 마지막 제출 시간이기 때문에 이 정보들을 dict에 기록하면서 진행하기로 했다.
최종 점수를 계속 갱신하는 것이 신경쓸 것이 좀 있어서 마지막에 한번에 모아서 계산하는거랑 그때그때 갱신하는 거랑 시간복잡도는 같다고
판단하여 마지막에 계산하였다.
그 후 팀 id와 3가지 요소를 튜플로 묶어서 리스트에 넣은 다음 lambda를 활용하여 sort 하였다.
동순위는 없으므로 팀 id가 t인 튜플의 인덱스+1을 한 것이 답이다.
"""

View File

@@ -0,0 +1,65 @@
# 비밀번호 발음하기
import sys
input = sys.stdin.readline
alphabet = "abcdefghijklmnopqrstuvwxyz"
VOWEL = "aeiou"
CONSONANT_OR_VOWEL = {a: 0 for a in alphabet} # 0이면 자음, 1이면 모음
for v in VOWEL:
CONSONANT_OR_VOWEL[v] = 1
def check_word(word):
is_vowel_in = False
sequence_count = [-1, 0] # sequence_count[0]은 자음(0),모음(1) 구분, sequence_count[1]은 count
before_w = ""
for w in word:
if not is_vowel_in and CONSONANT_OR_VOWEL[w] == 1: # 1번째 조건
is_vowel_in = True
if sequence_count[0] == CONSONANT_OR_VOWEL[w]: # 2번째 조건
sequence_count[1] += 1
if sequence_count[1] >= 3:
return False
else:
sequence_count[0], sequence_count[1] = CONSONANT_OR_VOWEL[w], 1
if before_w not in ("e", "o") and before_w == w: # 3번째 조건
return False
before_w = w
if not is_vowel_in: # 1번째 조건 최종 확인
return False
return True
def solution():
while 1:
word = input().rstrip()
if word == "end":
break
is_acceptable = check_word(word)
if is_acceptable:
print(f"<{word}> is acceptable.")
else:
print(f"<{word}> is not acceptable.")
return
solution()
"""
걸린 시간: 25분
시간 복잡도: 들어오는 모든 단어의 길이만큼의 시간이 걸린다.
해설: 자음과 모음 여부를 dictionary에 1, 0으로 O(1)에 구분할 수 있도록 세팅을 해둔다.
1번 조건은 한글자씩 볼 때 모음 여부에 따라 is_vowel_in 진위 여부를 결정한다.
2번 조건은 어떤 것이 몇 번 연속되고 있는지 현황을 기록하는 길이 2 리스트를 만들어서 판단한다. 기록과 다른 것이 나오면 초기화한다.
3번 조건은 이전 단어를 저장하는 변수를 만들고 이전 단어가 "e", "o"이면 통과하고, 아닐 경우 2번 연속되는지 확인한다.
"""

View File

@@ -0,0 +1,79 @@
# 크로스 컨트리
import sys
from collections import Counter
input = sys.stdin.readline
def exclude_team(teams):
excluded = set()
for team in teams:
if teams[team] != 6:
excluded.add(team)
return excluded
def find_winning_team(rank, excluded, team_count, n):
team_score = {i: {"count": 0, "score": 0} for i in range(1, team_count+1) if i not in excluded}
first_5th = {}
now_rank = 0
for r in rank:
if r in excluded:
continue
now_rank += 1
if team_score[r]["count"] >= 4:
if first_5th.get(r, 0) == 0:
first_5th[r] = now_rank
continue
team_score[r]["score"] += (now_rank)
team_score[r]["count"] += 1
winner = {"team": 0, "score": float("inf"), "5th": n+1}
for team in team_score:
if team_score[team]["score"] < winner["score"]:
winner["team"], winner["score"], winner["5th"] = team, team_score[team]["score"], first_5th.get(team, n+1)
elif team_score[team]["score"] == winner["score"] and first_5th.get(team, n+1) < winner["5th"]:
winner["team"], winner["score"], winner["5th"] = team, team_score[team]["score"], first_5th.get(team, n+1)
else:
continue
return winner["team"]
def game():
n = int(input().rstrip())
rank = list(map(int, input().rstrip().split()))
teams = Counter(rank)
excluded = exclude_team(teams)
result = find_winning_team(rank, excluded, len(teams), n)
return result
def solution():
t = int(input().rstrip())
for _ in range(t):
result = game()
print(result)
return
solution()
"""
걸린 시간: 50분 정도(score를 반대로 생각하거나, dictionary keyerror 때문에..) -> dict는 앵간하면 .get()으로 하자.
시간 복잡도: exclude_team()은 Counter 객체 전체를 순회하기 때문에 O(m)이다.
find_winning_team()은 rank 전체를 순회하고 팀 리스트를 순회하는 것인데 set과 dict를 쓰므로 O(1)이라서 전체는 m < n이므로 O(n)이다.
game()은 Counter이므로 O(m)에다가 exclude_team()과 find_winning_team()을 돌리기 때문에 전체는 O(n)이다.
마지막으로 전체 프로그램은 game()을 t번 반복하기 때문에 전체 시간복잡도는 O(t*n)이다.
함수별로 나눠놓으니까 전체 시간복잡도를 계산하는 것이 확실히 편하다.
해설: 먼저 6명이 아닌 팀을 counter로 알아낸 뒤 제외한다. 그 후 rank 리스트를 순차적으로 확인하면서 팀의 총점을 기록한다.
이때, 4등까지만 계산을 하도록 조건을 걸어주고, 팀의 5등이 전체 몇 등인지만 dict에 저장한다.
마지막에 총점을 비교하고, 같다면 5등까지 확인하는 절차를 거쳐서 최종 우승 팀을 결정한다.
"""

View File

@@ -0,0 +1,38 @@
# 돌 게임
import sys
input = sys.stdin.readline
def solution():
n = int(input().rstrip())
print("SK") if n%2 == 1 else print("CY")
return
solution()
"""
걸린 시간: 25분(dp로도 풀 수 있을 것 같아서 고민하다가..)
시간 복잡도: 그냥 % 계산 한번이니까 O(1)이다.
해설: 마지막에 무조건 창영이가 가져가기 때문에 상근이가 이기려면 돌을 남겨두면 안된다.
완벽하게 게임을 한다는 이야기는 서로 이기기 위해 최선을 다한 수만 생각한다.
k번째에 경기가 끝난다고 할 때, 상근이가 이기려면 k-1번째에 경기가 끝났을 때 1, 3개 중에 남아야 하고, 0, 2개가 남으면 창영이가 이긴다.
한 경기가 끝나면 2 or 4 or 6개의 돌이 없어지므로, 무조건 짝수개씩 사라지기 때문에 n이 짝수면 홀수개 즉, 1,3개가 절대 남을 수 없다.
반대도 마찬가지이므로 n이 짝수면 창영이의 승리, n이 홀수면 상근이의 승리이다.
dp로 만들어보면 dp[i]는 i개의 돌이 있을 때 상근이의 승리 여부이다. (dp[i]=1 -> 상근이 승리)
dp[i-1]에서 상근이가 이겼다면 1개 가져와서 이긴 경우, dp[i]는 1개가 남으므로 창영이 승리
dp[i-1]에서 상근이가 이겼다면 3개 가져와서 이긴 경우, dp[i]는 4개가 남으므로 어떻게 해도 창영이 승리
dp[i-1]에서 상근이가 진 상황도 위와 그대로 반대이다. 따라서 dp[i] = 1-dp[i-1]
초기 dp는 0번째만 필요하므로 dp[0]=1 (돌이 1개이고, 상근이가 시작하니까)
dp[i]의 적절한 상황을 구성하고, i상황이 되기 위해 연관되는 i 이전의 상황들을 상정하여 dp 식을 세우면 된다.
이번 상황은 i-1번째 상황만으로 i번째 상황이 결정된다.
"""