E144. Bài toán Иосифа

e-maxx algorithm original: C/C++ #algorithm #emaxx #misc
Văn bản bài toán được dịch từ tiếng Nga theo ngôn ngữ giao diện. Mã không thay đổi.

Источник: e-maxx.ru/algo, страница PDF 475.

Đề bài задачи. given натуральные

и

. По кругу выписывают все натуральные числа от 1 до

. Сначала

отсчитывают

-ое number, начиная с первого, и удаляют его. Затем от него отсчитывают

чисел и

-ое удаляют, и т. д. Процесс останавливается, когда остаётся одно number. it is required find это number. Bài toán была поставлена Иосифом Флавием (Flavius Josephus) ещё в 1 веке (правда, в несколько более

узкой формулировке: при

). Решать эту задачу можно моделированием. Простейшее моделирование будет работать

. Используя

Segment tree, можно произвести моделирование за

.

Lời giải за

Попытаемся find закономерность, выражающую ответ для задачи

через Lời giải предыдущих задач. С помощью моделирования построим таблицу значений, наVí dụ, такую: И здесь достаточно отчётливо видна следующая закономерность:

Здесь 1-индексация несколько портит элегантность формулы, если нумеровать позиции с нуля, то получится очень наглядная формула:

Итак, мы нашли Lời giải задачи Иосифа, работающее за

операций. Простая рекурсивная Cài đặt (в 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;

}

Lời giải за

Для сравнительно небольших

можно придумать более оптимальное Lời giải, чем рассмотренное выше

рекурсивное Lời giải за

. Если

небольшое, то даже интуитивно понятно, что тот Thuật toán делает много лишних действий: серьёзные изменения происходят, только когда происходит взятие по модулю

, а до этого

момента Thuật toán просто несколько раз прибавляет к ответу number

. Соответственно, можно избавиться от

этих ненужных шагов,

Небольшая возникающая при этом Complexity заключается в том, что после удаления этих чисел у нас получится Bài toán

с меньшим

, но стартовой позицией не в первом числе, а где-то в другом месте. Поэтому, вызвав рекурсивно себя

от задачи с новым

, мы затем должны аккуратно перевести результат в нашу систему нумерации из его собственной.

Также отдельно надо разбирать случай, когда

станет меньше

— в этом случае вышеописанная

оптимизация выродится в бесконечный цикл. Cài đặt (для удобства в 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;

}

Оценим асимптотику этого Thuật toánа. Сразу заметим, что случай

разбирается у нас старым

Lời giảiм, которое отработает в данном случае за

. Теперь рассмотрим сам Thuật toán. Фактически, на каждой

его итерации вместо

чисел мы получаем Ví dụно

чисел, поэтому общее number

итераций

Thuật toánа Ví dụно можно find из уравнения: логарифмируя его, получаем:

пользуясь разложением логарифма в ряд Тейлора, получаем приблизительную оценку:

Таким образом, Asymptotic complexity Thuật toánа действительно

.

Аналитическое Lời giải для

В этом частном случае (в котором и была поставлена эта Bài toán Иосифом Флавием) Bài toán решается значительно проще.

В случае чётного

получаем, что будут вычеркнуты все чётные числа, а потом останется Bài toán для

, тогда ответ для

будет получаться из ответа для

умножением на два и вычитанием единицы (за счёт сдвига позиций):

Аналогично, в случае нечётного

будут вычеркнуты все чётные числа, затем первое number, и останется Bài toán для , и с учётом сдвига позиций получаем вторую формулу: При реализации можно непосредственно использовать эту рекуррентную зависимость. Можно эту закономерность перевести в другую форму:

представляют собой последовательность всех нечётных

чисел, "перезапускающуюся" с единицы всякий раз, когда

оказывается степенью двойки. Это можно записать и в

виде одной формулы:

Аналитическое Lời giải для

Несмотря на простой вид задачи и большое количество статей по этой и смежным Bài toánм, простого аналитического представления решения задачи Иосифа до сих пор не найдено. Для небольших

выведены

некоторые формулы, но, по-видимому, все они трудноприменимы на практике (наVí dụ, см. Halbeisen, Hungerbuhler "The Josephus Problem" и Odlyzko, Wilf "Functional iteration and the Josephus problem").

C# lời giải

bản nháp tự động, xem lại trước khi gửi
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++ lời giải

đã khớp/gố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 lời giải

bản nháp tự động, xem lại trước khi gửi
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;
    }
}

Материал разбит как Thuật toánическая Bài toán: изучить постановку, понять асимптотику и реализовать Thuật toán на выбранном языке.

Vacancies for this task

việc làm đang hoạt động with overlapping task tags are đã hiển thị.

Tất cả việc làm
Chưa có việc làm đang hoạt động.