E144. 문제 Иосифа

e-maxx algorithm original: C/C++ #algorithm #emaxx #misc
선택한 UI 언어에 맞게 문제 텍스트를 러시아어에서 번역합니다. 코드는 변경하지 않습니다.

Источник: 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 표시됨.

전체 채용
아직 활성 채용이 없습니다.