https://www.acmicpc.net/problem/18790
풀이
1번째 시도(시간초과/메모리초과)
일단, 주어진 리스트에서 각 수가 몇 번 등장하는지를 센다.
가장 먼저 떠오른 건 DP. 대충 N의 범위가 500이니까 인자를 3개 잡는 재귀함수 형식으로 짜자.
인자는 다음과 같다.
dp[현재 처리하는 수][현재까지 쓴 수의 개수][현재 나머지]
마지막에 dp[N-1][N][0]을 불러서, 정답에 도달할 수 있는지 본다. dp 함수의 리턴값은 [현재 처리하는 수]가 몇 개 쓰였는지이다.
이 값을 이용해서 역추적할 수 있다.
랜덤하게 생성해낸 데이터에 대해서 잘 돌아가였고, 자신만만하게 제출하였으나,,,,

2번째 시도(시간초과/메모리초과)
dp 인자를 바꿔볼까?
주어진 리스트에서 각 수의 등장 횟수를 카운트하는 작업을 빼고,
dp[리스트에서 idx번째까지 수를 써서][현재까지 쓴 수의 개수][나머지] (나중에 알았는데 이게 에디토리얼에 설명된 풀이이다....?)
마찬가지로 마지막에 dp[2N-2][N][0]을 부르는 재귀함수 형식으로 짰다.
그런데 이 시도 방식이 1번째 시도 방식보다 2배 더 루프를 도는 거 아닌가?

여기서 약간 멘붕이 왔다. 혹시 dp의 인자가 3개가 아닌가?
3번째 시도(AC)
문득 이런 생각이 났다. 'idx번째 수까지 처리' 인자를 없애고, dp 테이블을 2차원으로 만들 수 있지 않을까? 근데 그러면 N 이 500정도로 작을리가 없는데?
여기서 깨달음을 얻었다.
아, dp테이블이 2차원이라 하더라도 O(N^3)일 수 있구나!
dp[현재까지 쓴 수의 개수][나머지]
0개 써서 나머지가 0, 즉 (0, 0)부터 스택에 집어넣는다. 그러니까 이제부터 탐색할 칸들을 스택에 집어넣겠다 이거다.
주어진 리스트들의 각 원소에 대해서, 스택에 들어가있는 칸 (i, j)들을 갱신 시도를 한다.
만약 dp[i+1][(j+arr[idx])%N]에 아무 값도 들어가있지 않다면, dp의 값을 현재 원소의 인덱스 값으로 한다.
그러면 나중에 역추적할 때 i+1번째로 사용한 값이 어떤 값인지 알 수 있다.
여기서 아무 값도 들어가있지 않을 때, 즉 이미 정해진 값을 이미 안 건드는 이유는,
해당 칸을 거쳐서 다른 칸으로 넘어가야하는데 만약 이 '거치는 칸'의 값이 변경되면 역추적이 불가능하기 때문이다.
이해를 돕기 위해, 문제에 주어진 예제의 경우 dp 테이블이 어떻게 채워지는지 적어놓겠다.

파이썬
import sys
N = int(input())
arr = list(map(int, input().split()))
dp = [[None]*N for _ in range(N+1)]
for i in range(N):
dp[0][i] = -1
stack = [(0, 0)]
for idx in range(2*N-1):
tmp = []
for i, j in stack:
if dp[i+1][(j+arr[idx])%N] is None:
dp[i+1][(j+arr[idx])%N] = idx
if i+1 <= N-1:
tmp.append((i+1, (j+arr[idx])%N))
stack += tmp
if dp[N][0] is None:
print(-1)
sys.exit()
now = 0
for i in range(N, 0, -1):
ans = arr[dp[i][now]]
print(ans, end = ' ')
now = (now - ans) % N
C++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int N; cin >> N;
int arr[2*N-1];
for (int i = 0; i < 2*N-1; i++) {
cin >> arr[i];
}
vector<vector<int>> dp(N + 1, vector<int>(N, -2));
for (int i = 0; i < N; i++) {
dp[0][i] = -1;
}
vector<pair<int, int>> stack;
stack.emplace_back(0, 0);
for (int idx = 0; idx < 2*N-1; idx++) {
vector<pair<int, int>> tmp;
for (auto [i, j] : stack) {
int ni = i + 1;
int nj = (j + arr[idx]) % N;
if (ni <= N && dp[ni][nj] == -2) {
dp[ni][nj] = idx;
if (ni <= N - 1) {
tmp.emplace_back(ni, nj);
}
}
}
stack.insert(stack.end(), tmp.begin(), tmp.end());
}
int now = 0;
for (int i = N; i > 0; i--) {
int ans = arr[dp[i][now]];
cout << ans << ' ';
now = (now - ans + N) % N;
}
}
다른 분들의 코드를 보니까 3인자 dp를 구현하기 위해 비트마스킹, 비트셋 등 다양한 최적화 기법들을 이용하셨는데 파이썬으로는 감히 못하겠다.
다음 편 예고
그런데 여기서 레전드 떡밥이 하나 있다.
그 어떠한 케이스를 짜도 절대로 -1, 즉 불가능한 경우가 나오지 않는다는 것이다????
여기에 대해서는 다음 시간에.....무려 D2짜리 18791 N의 배수 (2)를 도전해보며 적어보겠다.
'PS' 카테고리의 다른 글
| [Python] 백준 13141 - Ignition (0) | 2026.03.14 |
|---|---|
| [Python] 백준 32374 - 선물 고르기 (0) | 2026.03.12 |
| 일정한 주기를 가지고 올라갔다 내려갔다 하는 수열에서 나머지 어쩌구 하는 문제에 대한 고뇌 (0) | 2026.01.12 |
| [Python] 백준 13460 - 구슬 탈출 2 (0) | 2025.12.03 |
| [Python] 백준 18490 - 숫자 카드 제거 게임 (0) | 2025.10.05 |