E144. 문제 Иосифа
Источник: e-maxx.ru/algo, страница PDF 475.
문제 설명 задачи. given натуральные
и
. По кругу выписывают все натуральные числа от 1 до
. Сначала
отсчитывают
-ое number, начиная с первого, и удаляют его. Затем от него отсчитывают
чисел и
-ое удаляют, и т. д. Процесс останавливается, когда остаётся одно number. it is required find это number. 문제 была поставлена Иосифом Флавием (Flavius Josephus) ещё в 1 веке (правда, в несколько более
узкой формулировке: при
). Решать эту задачу можно моделированием. Простейшее моделирование будет работать
. Используя
Segment tree, можно произвести моделирование за
.
해법 за
Попытаемся find закономерность, выражающую ответ для задачи
через 해법 предыдущих задач. С помощью моделирования построим таблицу значений, на예제, такую: И здесь достаточно отчётливо видна следующая закономерность:
Здесь 1-индексация несколько портит элегантность формулы, если нумеровать позиции с нуля, то получится очень наглядная формула:
Итак, мы нашли 해법 задачи Иосифа, работающее за
операций. Простая рекурсивная 구현 (в 1-индексации):
int joseph (int n, int k) {
return n>1 ? (joseph (n-1, k) + k - 1) % n + 1 : 1;
} Нерекурсивная форма:
int joseph (int n, int k) {
int res = 0;
for (int i=1; i<=n; ++i)
res = (res + k) % i;
return res + 1;
}
해법 за
Для сравнительно небольших
можно придумать более оптимальное 해법, чем рассмотренное выше
рекурсивное 해법 за
. Если
небольшое, то даже интуитивно понятно, что тот 알고리즘 делает много лишних действий: серьёзные изменения происходят, только когда происходит взятие по модулю
, а до этого
момента 알고리즘 просто несколько раз прибавляет к ответу number
. Соответственно, можно избавиться от
этих ненужных шагов,
Небольшая возникающая при этом Complexity заключается в том, что после удаления этих чисел у нас получится 문제
с меньшим
, но стартовой позицией не в первом числе, а где-то в другом месте. Поэтому, вызвав рекурсивно себя
от задачи с новым
, мы затем должны аккуратно перевести результат в нашу систему нумерации из его собственной.
Также отдельно надо разбирать случай, когда
станет меньше
— в этом случае вышеописанная
оптимизация выродится в бесконечный цикл. 구현 (для удобства в 0-индексации):
int joseph (int n, int k) {
if (n == 1) return 0;
if (k == 1) return n-1;
if (k > n) return (joseph (n-1, k) + k) % n;
int cnt = n / k;
int res = joseph (n - cnt, k);
res -= n % k;
if (res < 0) res += n;
else res += res / (k - 1);
return res;
}
Оценим асимптотику этого 알고리즘а. Сразу заметим, что случай
разбирается у нас старым
해법м, которое отработает в данном случае за
. Теперь рассмотрим сам 알고리즘. Фактически, на каждой
его итерации вместо
чисел мы получаем 예제но
чисел, поэтому общее number
итераций
알고리즘а 예제но можно find из уравнения: логарифмируя его, получаем:
пользуясь разложением логарифма в ряд Тейлора, получаем приблизительную оценку:
Таким образом, Asymptotic complexity 알고리즘а действительно
.
Аналитическое 해법 для
В этом частном случае (в котором и была поставлена эта 문제 Иосифом Флавием) 문제 решается значительно проще.
В случае чётного
получаем, что будут вычеркнуты все чётные числа, а потом останется 문제 для
, тогда ответ для
будет получаться из ответа для
умножением на два и вычитанием единицы (за счёт сдвига позиций):
Аналогично, в случае нечётного
будут вычеркнуты все чётные числа, затем первое number, и останется 문제 для , и с учётом сдвига позиций получаем вторую формулу: При реализации можно непосредственно использовать эту рекуррентную зависимость. Можно эту закономерность перевести в другую форму:
представляют собой последовательность всех нечётных
чисел, "перезапускающуюся" с единицы всякий раз, когда
оказывается степенью двойки. Это можно записать и в
виде одной формулы:
Аналитическое 해법 для
Несмотря на простой вид задачи и большое количество статей по этой и смежным 문제м, простого аналитического представления решения задачи Иосифа до сих пор не найдено. Для небольших
выведены
некоторые формулы, но, по-видимому, все они трудноприменимы на практике (на예제, см. Halbeisen, Hungerbuhler "The Josephus Problem" и Odlyzko, Wilf "Functional iteration and the Josephus problem").
C# 해법
자동 초안, 제출 전 검토using System;
using System.Collections.Generic;
using System.Linq;
public static class AlgorithmDraft
{
// Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
int joseph (int n, int k) {
return n>1 ? (joseph (n-1, k) + k - 1) % n + 1 : 1;
}
int joseph (int n, int k) {
int res = 0;
for (int i=1; i<=n; ++i)
res = (res + k) % i;
return res + 1;
}
int joseph (int n, int k) {
if (n == 1) return 0;
if (k == 1) return n-1;
if (k > n) return (joseph (n-1, k) + k) % n;
int cnt = n / k;
int res = joseph (n - cnt, k);
res -= n % k;
if (res < 0) res += n;
else res += res / (k - 1);
return res;
}
}
C++ 해법
매칭됨/원본int joseph (int n, int k) {
return n>1 ? (joseph (n-1, k) + k - 1) % n + 1 : 1;
}
int joseph (int n, int k) {
int res = 0;
for (int i=1; i<=n; ++i)
res = (res + k) % i;
return res + 1;
}
int joseph (int n, int k) {
if (n == 1) return 0;
if (k == 1) return n-1;
if (k > n) return (joseph (n-1, k) + k) % n;
int cnt = n / k;
int res = joseph (n - cnt, k);
res -= n % k;
if (res < 0) res += n;
else res += res / (k - 1);
return res;
}
Java 해법
자동 초안, 제출 전 검토import java.util.*;
import java.math.*;
public class AlgorithmDraft {
// Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
int joseph (int n, int k) {
return n>1 ? (joseph (n-1, k) + k - 1) % n + 1 : 1;
}
int joseph (int n, int k) {
int res = 0;
for (int i=1; i<=n; ++i)
res = (res + k) % i;
return res + 1;
}
int joseph (int n, int k) {
if (n == 1) return 0;
if (k == 1) return n-1;
if (k > n) return (joseph (n-1, k) + k) % n;
int cnt = n / k;
int res = joseph (n - cnt, k);
res -= n % k;
if (res < 0) res += n;
else res += res / (k - 1);
return res;
}
}
Материал разбит как 알고리즘ическая 문제: изучить постановку, понять асимптотику и реализовать 알고리즘 на выбранном языке.
Vacancies for this task
활성 채용 with overlapping task tags are 표시됨.