Wikipedia

요세푸스 문제Permalink

counting-out 게임의 한 종류로써, 원으로 배치된 사람들을 계속해서 처형해나가는 게임이다.

계속해서 같은 방향으로 같은 수의 사람을 건너뛰며 사람을 처형한다.

제일 마지막까지 처형당하지 않은 사람이 생존자이다.

이것의 진행을 수열로 나타내보면 다음과 같다.

n=7n=7 이고 k=3k=3 이라 해보자. 77 명의 사람이 있고 33 명씩 건너뛰며 처형을 시킨다는 의미이다.

처음 사람의 번호를 00 번이라 하자.

0,1,2,3,4,5,6  초기 상태0,1,3,4,5,6  2번 제거0,1,3,4,6  5번 제거0,3,4,6  1번 제거0,3,4  6번 제거0,3  4번 제거3  0번 제거 \begin{aligned} &0,1,2,3,4,5,6&~~\text{초기 상태}\\ &0,1,3,4,5,6&~~\text{2번 제거}\\ &0,1,3,4,6&~~\text{5번 제거}\\ &0,3,4,6&~~\text{1번 제거}\\ &0,3,4&~~\text{6번 제거}\\ &0,3&~~\text{4번 제거}\\ &3&~~\text{0번 제거}\\ \end{aligned}

이렇게 최종 생존자는 33번(44번 째 사람)이 된다.

문제를 푸는 법Permalink

문제는 크게 마지막에 누가 살아남냐를 구하는 것과 처형당하는 순서까지 구하는 것이 있는데, 전자는 수학적으로 생각해야하고, 후자는 노가다를 해야한다.

처형당하는 순서까지 구하는 경우Permalink

문제의 예시이다.

BOJ 1158 - 요세푸스 문제

N,K5,000N, K \le 5,000

BOJ 1168 - 요세푸스 문제 2

N,K100,000N, K \le 100,000

N,KN, K가 작을 때는 아무 자료구조나 써서 직접 그 다음 KK 번 째가 뭔지를 찾아주어서 처형시켜주는 것으로 순열을 구할 수 있다.

N,KN, K가 큰 22번 문제같은 경우, 세그먼트트리를 활용하여 정말 이분탐색을 쓰든 뭘 쓰든 해서 구해주면 된다.

요세푸스 순열을 구하는건 이 글의 주된 내용이 아니고 그냥 PS적인 지식으로 충분히 풀리는 구현이므로 다루지 않는다.

생존자가 누구인지만 구하는 경우Permalink

위키피디아에 있는 순서대로 필요한 것만 정리를 해보자.

f(N,K)f(N, K) 을 최종 생존자 번호라고 하자.

K=2K=2Permalink

NN이 짝수라고 하자. 그럼 처음에는 2,4,,N2,4,\dots,N 처럼 제거가 된다.

이제 남아있는 수들인 1,3,,N11,3,\dots,N-1 들은 원래 처음 1N1 \sim N의 순열에서

현재 자신의 위치를 xx 라고 한다면, 2x12x-1 에 있던 수들이다.

M=N2M=\dfrac N2 라고 한다면, f(M)f(M) 은 원래 f(2M)f(2M) 에서 2x12x-1 에 있던 수이므로

f(N)=f(2M)=2f(M)1   if  2N \begin{aligned} f(N)=f(2M)=2f(M)-1 ~~~ \text{if}~~2 \mid N \end{aligned}

NN이 홀수라고 하자.

11번 사람은 첫 번째 싸이클이 끝날 때 처형당한다. 그 이후에는 새로운 22번 째, 새로운 44번 째 사람이 처형당하기 시작한다.

이 경우, xx에 있던 사람은 원래 2x+12x+1 에 있었다.

새로운 남은 사람들의 번호는 3,5,7,3,5,7,\dots 이므로 확인해보면 맞을 것이다.

따라서, M=N2M=\left\lfloor \dfrac N2 \right\rfloor 라면,

f(N)=f(2M+1)=2f(M)+1 f(N)=f(2M+1)=2f(M)+1

결국 K=2K=2 일 땐, O(log2N)O(\log_2 N) 에 답을 구할 수 있다.

K=2K=2 일 때를 표로 정리해보면

n 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
f(n)f(n) 1 1 3 1 3 5 7 1 3 5 7 9 11 13 15 1

와 같이 된다.

보면 22의 거듭제곱은 항상 11이고, 계속 22씩 증가해나가는 홀수 증가 수열을 보인다.

따라서 n=2m+l  (0l<2m)n=2^m+l~~(0 \le l < 2^m) 이라면, f(n)=2l+1f(n)=2 \cdot l + 1 이여야 한다.

이렇게도 생각할 수 있다. ll 명이 죽은 다음에, 2m2^m 명이 남아 있다면, 2l+12l+1 번 째 사람이 순열의 처음에 오도록 위치되기 때문에 생존자 2l+12l+1 이다

귀납적 증명은 사이트를 참고하자.

모든 KKPermalink

사실 KK가 뭐든간에 상관없이 항상 빠르게 정답을 구할 수 있다.

K=2K=2는 그냥 정리해본 내용이다.

위키피디아엔 11부터 시작하는 인덱스로 설명이 되어있지만, 00으로 설명하자. 그래야 더 수식과 코드가 간단하다.

사람이 NN명 있고(0,1,,N10,1,\dots,N-1) KK 명씩 건너뛰며 처형한다고 해보자.

처음에 K1K-1 번째 사람이 처형당한 후에의 게임은 K(modN)K \pmod N 번 째 사람이 11번이 되어 시작되는 게임이라고 볼 수 있다.

f(N,K)f(N,K) 를 생존자의 위치라고 할 때, 처음 한 명이 처형당한 후에는 어쨌든 N1N-1 명이 남았으니까 f(N1,K)f(N-1,K) 라는 값이 중요하다.

그런데 첫 번째 사람이 00이 아닌 KK로 바뀐 게임이므로

f(N,K)=f(N1,K)+K(modN) f(N,K) = f(N-1,K)+K \pmod N

이 된다.

f(1,K)=0f(1,K)=0 이므로, N=1N=1 일 때부터 반복하면 O(N)O(N)f(N,K)f(N, K)를 구할 수 있다.

예시

N=7,K=3N=7, K=3 이라고 하자.

0,1,2,3,4,5,6 0,1,2,3,4,5,6

여기서 22 를 처형한 후의 상태는

0,1,3,4,5,6 0,1,3,4,5,6

이고, 이제 3(=K)3(=K) 가 첫 번째 사람이 되었다.

그런데 N=7N=7 이였으므로 33 보다 왼쪽에 있는 사람들은 77을 더해주고 오른쪽으로 보내보자.

3,4,5,6,7,8 3,4,5,6,7,8

여기서 이제 3(=K)3(=K) 를 빼면 정확히 f(6,3)=f(N1,K)f(6,3)=f(N-1,K) 인 상태, 0,1,2,3,4,50,1,2,3,4,5 가 되고, 위의 수식이 옳음을 알 수 있다.


더 빠른 접근Permalink

위의 방법은 O(N)O(N) 이지만, KK 가 충분히 더 작다면, 놀랍게도 더 빠르게 구할 수 있다.

이 방법의 시간복잡도는 O(KlogN)O(K \log N)이다.

수식을 먼저 살펴본다.

f(N,K)={0 if n=1(f(N1,K)+K) % N if 1<N<Kfor hf(NN/K,K)(N % K),  {h+n    if h<0h+hK1   otherwise if KN f(N,K)=\begin{cases} 0&\text{~if~}n=1 \\ (f(N-1,K)+K) ~\%~ N & \text{~if~}1 < N < K \\ \text{for~}h \coloneqq f(N-\left\lfloor N/K \right\rfloor,K)-(N ~\%~K),~~\begin{cases} h+n ~~~\text{~if~}h<0 \\ h+\left\lfloor \dfrac h{K-1} \right\rfloor ~~~ \text{otherwise} \end{cases}& \text{~if~}K \le N \end{cases}

일단 N=1N=1 일땐 f(N,K)=0f(N, K)=0 임은 자명하다.

1<N<K1 < N < K 일 때도, KKNN 보다 더 작지 않으면 O(KlogN)O(K \log N) 이란 시간복잡도가 쓸모가 없으므로 앞서 구했던 수식인 f(N,K)=f(N1,K)+K(modN)f(N,K)=f(N-1,K)+K \pmod N 을 이용해준다.

그렇지 않고 KNK \le N 인 경우를 보자.

K,2K,3K,K,2K,3K,\dots 번 째 사람들을 모두 죽였다고 생각해보자.

한번에 NK\Bigl\lfloor \dfrac NK \Bigr\rfloor 명을 처형한 것과 같다.

남은 수는 NNKN-\left\lfloor \dfrac NK \right\rfloor 명이고, 이제 f(NNK,K)f\left(N-\left\lfloor \dfrac NK \right\rfloor, K\right) 라는 값이 우리의 관심사이다.

Stack Overflow 답변 를 참고하면,

N=10,K=3N=10, K=3 이라고 하자. 처음에 33 명이 죽는다. 이제 N7N \coloneqq 7 이다.

0 1 2 3 4 5 6 7 8 9    n=10, k=3
1 2   3 4   5 6   0    n=7,  k=3

여기서 N=7N=7 인 쪽에 있는 사람들의 넘버링이 어떻게 N=10N=10 쪽에서 변환된건지 살펴보자.

일단 처음에 909 \to 0 이 된건 10%310 \% 3 이 더해진 것이다. 따라서 모든 수가 N % KN ~\%~ K 만큼 더해졌다고 하면 f(NN/K,K)f(N-\left\lfloor N/K \right\rfloor,K) 에서 N % KN ~\%~ K 를 뺐다고 하자.

보면 N=7N=7 에서 1,21, 2 그룹은 00이 더해지고, 3,43,4 그룹은 11이 더해지고, 5,65, 6 그룹은 22가 더해짐이 보인다.

이건 K1K-1 개씩 그루핑이 되있기 때문에 hK1\left\lfloor \dfrac h{K-1} \right\rfloor 를 더 더해줘야 한다.

그리고 h<0h<0 이라면 그냥 NN 을 더해줘서 양수로 만들어주는 것으로 정답이 된다.

위 경우에서 N=7N=7 일 때 00이 정답이 되어서 N % KN ~\%~ K를 뺐더니 1-1 이 되어 N=10N=10 일 때, 정답이 99 가 되는 경우를 가정해보면 된다.

실제 정답은 N=7N=7 에서 00은 아니다.

연습 문제Permalink

BOJ 11025 - 요세푸스 문제 3

O(N)O(N) 으로 풀 수 있다.

void solve() {  
   int k, n;  
   cin >> n >> k;  
   int ans = 0;  
   for (int i = 0; i < n; i++)  
      ans = (ans + k) % (i + 1);  
   cout << ans + 1;  
}

BOJ 1179 - 마지막 요세푸스 문제

O(KlogN)O(K \log N)으로 풀 수 있다.

ll k, n;  
ll f(ll n) {  
   if (n == 1) return 0;  
   if (k > n) return (f(n - 1) + k) % n;  
   ll h = f(n - n / k) - n % k;  
   if (h < 0) return h + n;  
   else return h + h / (k - 1);  
}  
  
void solve() {  
   cin >> n >> k;  
   if (k == 1) cout << n;  
   else cout << f(n) + 1;  
}

Comments