Ответы пользователя по тегу Алгоритмы
  • Как расставить скобки в выражении всеми возможными способами? какова сложность оптимального алгоритма?

    @Mercury13
    Программист на «си с крестами» и не только
    Тут чёткое динамическое программирование. У нас N+1 операнд, и N операций.
    Создаём кэш: для всех L и R, 0<=L<=R<=N, мы храним:
    • Операцию: null, +, ×, many.
    • Ассоциативный массив:
    •• Ключ — число (целое/рациональное/плавающее/фиксированное — с какими хотите работать).
    ̃•• Значение — тройка:
    ••• Номер операции с макс. приоритетом.
    ••• Значение слева (такое же число).
    ••• Значение справа (такое же число).
    Для всех L=R кэш заполнен такой величиной:
    • Операция — null.
    • В массиве один элемент: ключ — операнд, значение — любая затычка.
    Для кэша годится, например, матрица (N+1)×(N+1).

    После этого проходимся по диагоналям нашего кэша, обрабатывая сначала L=0..N−1, R=L+1, затем L=0..N−2, R=L+2, и т.д., пока на дойдём до диагонали из одного элемента: L=0, R=N. Как именно обрабатываем?
    1. Смотрим операцию, записанную в кэше (L+1, R), и L-ю операцию в выражении. Если вторая — «сложить» или «умножить», а первая — null или совпадает, записываем эту операцию в кэш на место (L, R). Иначе — пишем many.
    2. Вычисляем R1: если в кэше на месте (L, R) есть какая-то операция, то L; если many — то R−1 (оптимизация по ассоциативности).
    3. Проходимся по всем M=L…R1.
    3.1. Двойным циклом проходимся по содержимому кэша в (L,M) и (M+1, R). Обозначим переменные u и v.
    3.1.1. Записываем в кэш на место (L,R), если таковое отсутствует:
           • ключ u (+−×/) v
           • номер операции — M
           • значения слева и справа — u и v.
    Рассмотрим, например 1 + 2 + 3 − 4.
    (0, 0) — null; (1 → ? ? ?)
    (1, 1) — null, (2 → ? ? ?)
    (2, 2) — null, (3 → ? ? ?)
    (3, 3) — null, (4 → ? ? ?)

    UPD: Операнды у наc { 1 2 3 4 }, орерации { + + − }. Ну разумеется.

    Теперь смотрим, что будет в (0, 1). В (1, 1) в кэше null, нулевая операция + — операция +. Одна итерация, R1=0.
    Прокрутив эту итерацию, получаем…
    (0, 1) — +; (3 → 0 1 2). Это означает: получили 3, сложив 0-м плюсом 1 и 2.
    Аналогично…
    (1, 2) — +; (5 → 1 2 3)
    (2, 3) — many; (−1 → 2 3 4)

    Вторая диагональ. (0, 2). В позиции (1, 2): в кэше +, 0-я операция +. Записываем + и прокручиваем одну итерацию, от 0 до 0. Т.е. складываем (0, 0) и (1, 2).
    (0, 2) — +; (6 → 0 1 5).
    Теперь (1, 3). Операция будет many; прокручиваем и получаем:
    для M=1 — складываем (1, 1) и (2, 3), и получаем (1 → 1, 2, −1).
    для M=2 — вычитаем (1, 2) и (3, 3), и получаем (1 → 2, 5, 4).
    Если без дублей…
    (1,3) — many; (1 → 1 2 -1)

    Ну и, наконец, пошли (0, 3). Операция будет many…
    для M=0 — складываем (0, 0) и (1, 3), и будет (2 → 0 1 1).
    для M=1 — складываем (0, 1) и (2, 3), и будет (2 → 1 3 −1).
    для M=2 — вычитаем (0, 2) и (3, 3), и будет (2 → 2 6 −4). Повторим, что это значит: получилось 2, из-за того, что вычли 2-м минусом 6 и −4.
    Итого, без дублей, (0, 3) — many, (2 → 0 1 1).

    Так «повезло», что у нас вышел один результат. Но в результате объединения может быть и множество.

    А теперь вопрос: откуда мы получили 2-ку? Смотрим в (0, 3) и получаем: 2 = 1 +0 1. Левую единицу раскрываем через (0, 0), правую — через (1, 3): 2 = 1 +0 2 +1 (−1).
    Остаётся раскрыть −1, и получаем 2 = 1 + 2 + 3 − 4.
    Это можно сделать рекурсивно.

    Если точный путь вычислений не нужен, ассоциативный массив превращается в множество без повторов. Не (2 → 0 1 1), а просто 2.
    Во многих вариантах динамического программирования, если прямой ход не нужен, двухмерный кэш можно превратить в одномерный. Тут я не вижу способа. Написал, да понял, что ошибся.

    UPD. А сложность какая? — у меня выходит 2n·n². Почти уверен, что реально цифра меньше, просто думать над этим облом.
    Ответ написан
    Комментировать
  • Что значит последовательный участок памяти?

    @Mercury13
    Программист на «си с крестами» и не только
    Один последовательный участок памяти — это значит «последовательные адреса памяти». Таковыми будут массив простой и динамический, строка традиционного устройства, и все структуры памяти, что на них основаны. В том числе простейший стек и кольцевая очередь.

    Для чего это нужно?
    1. Это ближе к железу: проще код, лучше работает кэш, а значит, быстрее.
    2. Некоторые функции, особенно нешаблонные, для своей работы требуют именно последовательных ячеек памяти.

    Но если я реализую стек через список, разве это не будет противоречить сказанному?

    Совершенно верно, будет. Отсюда и знаменитая ошибка «переполнение стека» — непонятно, как наладить стек вызовов бесконечной ёмкости и относительно простого устройства. Потому, если нужна очень глубокая рекурсия (например, при обходе сетей), стек вызовов приходится эмулировать.
    Ответ написан
    7 комментариев
  • Как работает quicksort?

    @Mercury13
    Программист на «си с крестами» и не только
    Первое.
    Ваш код явно неортодоксальный, и, по-моему, он делает лишние сортировки.
    UPD. Алгоритмическая сложность такого кода порядка O(n² log n), более точную границу подобрать не могу, да и имеет ли смысл?

    Второе.
    Пусть массив отсортирован по убыванию, слева — одно число, справа — сто, делящий 100. Скажем, 101, [100], 99…0.
    Тогда i = 0, j = 101, и имеем массив 0, 100, 99…1, 101.
    Теперь i = 1, j = 100; arr[i]<pivot в пролёте, arr[j]>pivot в пролёте, i<j, и проводим обмен 0, 1, 99…2, 100, 101.
    На третьем шаге выходит 0, 1, 2 98…3, 99, 100, 101. И это при том, что делящий 100 и он давно встал на место.
    Когда мы прокрутим итерацию до конца, массив будет полностью отсортирован по возрастанию.
    Вывод: делящий элемент не обязательно будет точно делить массив, хоть многое что крутится вокруг него. И если будет сильный дисбаланс, часть элементов перейдёт из большего массива в меньший.
    Ответ написан
    Комментировать
  • Есть ли алгоритм для наиболее быстрого нахождения включает ли одна фигура другую?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Надо указывать не только то, что все точки первой фигуры внутри второй, но и то, что все точки второй фигуры снаружи первой.
    2. Даже примитивный алгоритм даёт скорость O(mn). Сколько у вас примерно точек?
    3. Можно воспользоваться эвристиками. Если охватывающий прямоугольник 1-й фигуры целиком во 2-й — ДА. Если вылезает из охватывающего прямоугольника 2-й — НЕТ.
    4. Можно поступить так. Отсортируем все точки той фигуры, которую проверяем на точки, по X-координате. Также отсортируем все отрезки той фигуры, которую проверяем на отрезки, по X-координате левой точки. Указатель отрезков ставим в начало перечня отрезков. Заводим список активных отрезков. Проходимся по всем точкам, поступая так…
    • Для всех отрезков в списке активных: если точка вышла из [X1, X2) — исключить из списка!
    • Смотрим на тот отрезок, что под указателем. Если X точки >= X1 — идём по фигуре дальше; если заодно X < X2 и Y выше отрезка (X1,X2 — (Y1,Y2) — включаем отрезок в список.
    При определённом устройстве списка (куча по X2) имеем O(n log n). И если меньшая фигура имеет сложность примерно как у большей (т.е. не логарифм) — имеем выигрыш.
    Ответ написан
  • Как найти маркер конца текста?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    Придумал. Пусть Σ' — это наш алфавит, из которого можно делать маркеры.
    1. Определяем макс. длину маркера — mlen = ceil( log{|Σ'|}(length + 1) ).
    2. Заводим битовую маску длины |Σ'| + |Σ'|² + … + |Σ'|^{mlen−1} + length + 1.
    Каждый возможный маркер обозначим числом:
    A = 0, B = 1, …, Z = 25, AA = 26, AZ = 51, BA = 52…
    3. Очередь пуста.
    4. Если очередной символ из Σ' — смотрим, сколько символов сзади из Σ', и ставим единицы в соотв. позициях маски.
    5. Находим в маске ноль — это и есть нужный нам маркер.
    Ответ написан
    Комментировать
  • Как решить проблему поворота вектора в сторону заданных координат?

    @Mercury13
    Программист на «си с крестами» и не только
    sin(x^y) = x×y / (|x|·|y|)       (1-й курс института, но легко выводится и самостоятельно)
    cos(x^y) = (xy) / (|x|·|y|)      (школа)

    x, y — векторы в 2D
    x^y — угол между векторами
    x×y — косое произведение векторов
    (xy) — скалярное произведение векторов
    |x| — евклидова длина (модуль, 2-норма) вектора

    Отсюда x^y = atan2( (xy), x×y ).
    Возможно, где-то упустил знак и у меня получился не x^y, а y^x.
    Ответ написан
  • Как создать эффект камеры?

    @Mercury13
    Программист на «си с крестами» и не только
    У меня по этому поводу целый перевод есть.
    Первая часть: https://habrahabr.ru/post/272933/
    Вторая часть: https://habrahabr.ru/post/273397/

    Именно по поводу того, как камерой вести персонажа.
    Ответ написан
    1 комментарий
  • Как решить данную задачу за O(log(n)*n)?

    @Mercury13
    Программист на «си с крестами» и не только
    Создадим массив на 2N событий. Событие — эти тип («начало»/«конец»), № круга и координата. Каждый круг создаёт два события: начало на i−R и конец на i+R.
    Массив сортируем по X, с таким уточнением, если X равны.
    • Если касание считать пересечением, начало < конец (т.е. для точки X идут начала, затем концы). В такой ситуации нам не важны номера кругов.
    • Если нет — сначала номера кругов (aka x-координаты центров) по возрастанию, затем начало < конец.
    Заводим счётчик кругов. Проходимся по массиву.
    • Если видим начало — к результату +счётчик, к счётчику +1.
    • Если видим конец — к счётчику −1.

    В нашем примере:
    Счётчик 0, результат 0
    Начало №1 на x=−4: результат 0, счётчик 1
    Начало №0 на x=−1: результат 1, счётчик 2
    Начало №2 на x=0: результат 3, счётчик 3
    Начало №4 на x=0: результат 6, счётчик 4
    Конец №0 на x=1: счётчик 3
    Начало №3 на x=2: результат 9, счётчик 4
    Затем аж два конца на x=4, счётчик опустился до 2
    Начало №5 на x=5: результат 11, счётчик 3
    Конец №5 на x=5: счётчик 2
    И ещё два конца сбрасывают счётчик до 0.

    UPD. Уточнил для случая, если касание — не пересечение.
    Ответ написан
    Комментировать
  • Как удалить элемент из вектора, а затем добавить несколько новых?

    @Mercury13
    Программист на «си с крестами» и не только
    У вас могут быть две проблемы.
    1. Неверно написана операция «присвоить» или «переместить».
    2. Забыл, что в std::vector при операции «добавить» или «удалить» возможно физическое перемещение объекта и ссылки на него больше недействительны.

    Ну и IsEnded лучше писать вот так.
    bool IsEnded(const Query &aVar) { return (aVar.id == NO_ID); }
    Ответ написан
    2 комментария
  • План подготовки для поступления в Яндекс ШАД?

    @Mercury13
    Программист на «си с крестами» и не только
    Алгоритмы. Немного олимпиадного программирования ОЧЕНЬ не помешает. Алгоритмы там предлагают несложные, но очень нетривиальные, надо чувствовать, как решить задачу. Элементы сложности алгоритмов. Две задачи из восьми гарантированно будут.

    Алгебра и дискретная математика. Первый курс, всё скопом, без доказательств. Линейные уравнения, квадратичные формы, матрицы, собственные векторы, жорданова форма, перестановки, графы, теория множеств, комбинаторика, алгебра логики…

    Интегралы (не слишком «злые», но приёмы «подстановка», «по частям» и «тригонометрический интеграл» всё же освоить стоит). Интеграл средней сложности — постоянный гость в ШАДý. Может быть и ещё одна задача из мутьанализа — но это как повезёт и задача будет гарантированно нетривиальная, но решающаяся на «том, что помнишь с института» — дифференцирование, ряды Тейлора, основы топологии, простейшие пределы, правило Лопиталя. Вспомни, как берутся простейшие двойные интегралы, может попасться, например, на теории вероятностей.

    ФКП. Самое начало. Аналитических функций и рядов Лорана точно не будет. А вот то, что в комплексном поле многочлен n-й степени имеет n корней, знать надо.

    Теория вероятностей. Непрерывные и дискретные вероятности. Нечто несложное, почти что на уровне кубиков и карт, но одна-две из восьми будет. Хотя статистика — важная часть ШАДа, на экзамене не требуют. И пекла типа белых шумов и интегралов Ито не будет. Хотя что-то типа дискретной марковской цепи — а вдруг, хотя знакомые мне три экзамена не было.

    Школьные олимпиадные задачи. Возможна одна.

    Итого.
    Две — алгоритмы.
    Одна-две — вероятность.
    Одна — интеграл.
    Две-три — что угодно из школьной математики, дискретной математики, матанализа, алгебры, ФКП…

    P.S. Очень хороший приём, который мне помог. Конечно, вам придётся держать скан какого-нибудь справочника или распечатку Википедии (это не возбраняется, но электроника запрещена — впрочем, калькулятора задачи не требуют). Печатайте на одной стороне, вторую — на черновик!
    Ответ написан
    4 комментария
  • Непонятка с сортировкой - почему random более плохой случай, чем decremental array?

    @Mercury13
    Программист на «си с крестами» и не только
    Для пузырька убывающий массив действительно худший возможный случай, и единственное, что могу предположить,— JIT и предсказание ветвлений на уровне процессора. Ради интереса перейди на устаревшую виртуальную машину — думаю, этот случай сразу же станет худшим.

    Для быстрой убывающий массив — один из лучших случаев, будет сделано ровно [n/2] обменов. Легко показать, что элементы в начале и конце массива, стоящие на своих местах, быстрая сортировка не сместит никогда. Так что первое, что сделает сортировка,— разобьёт на меньший (отсортированный) и больший (частично отсортированный как надо, частично — в обратном порядке). Ту часть, которая как надо, сортировка не тронет. А ту, которая наоборот — задействуем трансфинитную индукцию, и получаем, что ровно [n/2].
    Ответ написан
    Комментировать
  • Как на основе расстояния Левенштейна вывести промежуточные слова?

    @Mercury13
    Программист на «си с крестами» и не только
    Разумеется, будем считать, что слова не должны быть осмысленными (иначе это другая задача, где этот алгоритм, с одной стороны, тяжеловат, с другой — совсем не нужен). Если между словами «коза» и «волк» расстояние 3, то промежуточные слова — «коза → воза → вола → волк». Или, если не против, «коза → козк →колк → волк».

    Итак, перед нами типичный алгоритм динамического программирования. В алгоритме динамического программирования есть два этапа: обратный ход и прямой. Этот алгоритм страдает двумя недостатками, мешающими взять и написать прямой ход.
    1. Матрица сжата. Вместо матрицы (M+1)×(N+1) мы имеем два массива длины N+1, хранящие одну строчку и потихоньку перезаписываемые.
    2. Нет второй матрицы, которая указывает направление, куда идти. В нашем случае направления — это три действия над строкой: замена, вставка и удаление.
    ну и 3) В сокращённом варианте хватит двух массивов, обмениваемых местами. К делу отношения не имеет, просто вопросы умелого программирования. Кажется, перед нами Java и утечек памяти тут нет — тем лучше.

    С задачами 1 и 2 надо сделать три вещи.
    1. Массивы D1 и D2 превратить в единую матрицу (M+1)×(N+1).
    2. Добавить второй такой же. Элементы его — enum: удалить/вставить/заменить.
    3. Написать прямой ход. Мы находимся в точке (M, N); смотрим, куда идти. Заменить — идём туда-то, вставить — идём туда-то, удалить — идём туда-то. Ну и, разумеется, выполняем нашу операцию на строках и выводим, что получилось. Продолжаем путь, пока не попадём в (0, 0).
    Ответ написан
  • Картинка из картинок.Как сделать??

    @Mercury13
    Программист на «си с крестами» и не только
    Простейший вариант…
    1. Для каждой малой картинки выбрать «средний» цвет.
    2. Уменьшить исходное изображение до M×N px, и в каждый пиксель вместо сплошного цвета подставить ту картинку, которая больше всего подходит по цвету.

    Алгоритм можно совершенствовать — например, подставлять одну из 10 наиболее подходящих, а если в радиусе, скажем, 30 единиц есть куда больше 10 картинок — брать их все. А можно ещё использовать метод коррекции ошибок Флойда-Стейнберга (наиболее удачный метод при переводе картинок в N цветов)
    Ответ написан
    3 комментария
  • Существует ли какой-нибудь алгоритм склонения существительных во множественном числе?

    @Mercury13
    Программист на «си с крестами» и не только
    Для 98% существительных — есть, и для этого копай шаблоны Викисловаря. Правда, так просто, по одному только слову «автомобили», нельзя, ведь тупая машина никак не поймёт, что «вижу автомобилев» — это вдвойне неверно (не угаданы группа и одушевлённость).
    Надо задать:
    • склонение (первое / второе / третье / разносклоняемое / несклоняемое);
    • группу склонения (десятка полтора на каждое склонение);
    • одушевлённость (влияет на дательный и винительный падежи, в зависимости от склонения).
    Ответ написан
    3 комментария
  • Как нужно изменить алгоритм Дейкстры чтобы он искал самый длинный путь?

    @Mercury13
    Программист на «си с крестами» и не только
    Варианты.
    1. Если граф циклический, максимальный путь — ∞.
    2. Если циклический, но путь обязан быть несамопересекающимся — Дейкстра не подойдёт. Подобную олимпиадную задачу я решал и там решением был перебор с кэшированием (вершин вроде до 15).
    3. Если граф циклический, но есть отрицательные веса, которые в определённых случаях дают-таки точный максимум — меняем знак, применяем модификацию Дейкстры для отрицательных весов. Он либо скажет, что есть цикл, позволяющий сколь угодно уменьшить сумму, либо даст точный минимум.
    4. Если ациклический ненаправленный — то либо один, либо нет вообще (т.н. лес);
    5. Если ациклический направленный — должен работать совсем другой алгоритм: отсортировать в топологическом порядке, убрать те элементы, которые перед началом и за концом, а на оставшихся пустить динамическое программирование.
    Ответ написан
    Комментировать
  • Алгоритм генератора заявок для моделирования СМО с пуассоновским потоком?

    @Mercury13
    Программист на «си с крестами» и не только
    class Event {  //  интерфейс
    public:
      virtual double time() = 0;   // когда потребуется внимание со стороны менеджера событий
      virtual void process() = 0;  // команда обработать событие
      // virtual int type() = 0;      // можно пристроить, если по какой-то причине надо отличать кресло от генератора клиентов
    };
    
    class EventSink {  // интерфейс
      virtual void add(Event& x);
    }
    
    class Shop;
    
    class Chair : protected Event {
    private:
      double fTime;
      bool fIsOccupied;
    public:
      Shop& shop;
      virtual double time() override { return fTime; }
      virtual void process() override;
      {
         что-то типа…
         если shop.nWaiting != 0 {
            --shop.nWaiting;
            occupy(time);
         } else {
             isOccupied = false;
         }
      }
      void occupy (double aTime) {
            time = aTime + время обслуживания клиента
            shop.manager.add(*this);
       }
       bool isOccupied() { return fIsOccupied; }
    };
    
    class CustomerGenerator : protected Event {
       // Устроен аналогично Chair, служит для генерации потока клиентов
    public:
      virtual void process() override {
         если shop.nWaiting == 0 и кресло свободно {
            shop.chair.occupy(fTime);
         } else {
            ++shop.nWaiting;
         }
         fTime += экспоненциальное время ожидания;
         manager.add(*this);
      }
      void init() {
         fTime = 0;
         process();
      }
    };
    
    class Shop {
    public:
       Chair chair;  // можно несколько кресел
       CustomerGenerator generator;
       int nWaiting;  // сколько человек в очереди
       EventSink& manager;
    }
    
    class Manager : public EventSink {
       Event* events[2];   // события отсортированы по времени
       int nEvents;
    
       void add(Event& x) override {
          // вставить двоичным поиском x в нашу очередь;  тут же можно сделать ограничение
          // «обработать 1000 клиентов» или «работать 8 часов».
       }
    
       И самое главное — «жизненный цикл».
       shop.generator.init();
       Пока очередь не пуста…
       • вытащить из неё событие
       • process его!
    }


    Как оно работает?
    1. на 5-й минуте приходит посетитель. Значит, генератор вносится в очередь со временем 5 минут.
    2. Выбираем генератор из очереди. Поскольку в парикмахерской очереди нет, он садится в кресло (в список вносится кресло со временем 5+8 = 13 минут), в список вносится генератор со временем 5 + 3 = 8 минут.
    3. Из этой пары выбираем генератор (у него 8 минут, против 13 минут у кресла). Он вносит нашего товарища в очередь (теперь в очереди один), в очередь вносится генератор со временем 8+4 = 12 минут.
    4. Снова в очереди кресло и генератор, и минимальное время у генератора. Теперь в парикмахерской ждут двое, и генератор вносим в очередь на время 12 + 30 = 42 минуты.
    5. Теперь минимальное время у кресла, и оно обслуживает ещё одного посетителя, внося себя в очередь на (13 + 8 = 21 минуте.
    6. И снова минимальное время у кресла, оно вносит себя в очередь на 21 + 8 = 29 минут.
    7. Пришла 29 минута, но стричь-то некого! В очереди менеджера остался один генератор.
    8. Выбираем из очереди генератор, и так далее…

    (простите, что я написал сумбурно, ведь есть очередь событий в менеджере и очередь клиентов в парикмахерской).

    Не забудьте, что события добавляют не в хвост очереди, а куда угодно — ведь событие может, в свою очередь, каскадом вызвать другие события.
    Ответ написан
  • Как правильно указать промежуток чисел,при котором будет правильно выбран падеж слова?

    @Mercury13
    Программист на «си с крестами» и не только
    Вот правила множественного числа разных языков.
    www.unicode.org/cldr/charts/latest/supplemental/la...

    Я с подобным работал, и если есть возможность выдавать один и тот же ответ в двух разных ветках — то так.
    Остаток от деления на 100 от 5 до 20 → «ворон»
    Остаток от деления на 10 равен 1 → «ворона»
    Остаток от деления на 10 от 2 до 4 → «вороны»
    Иначе — «ворон».

    Я эти правила записывал вот в таком виде.
    <pluralRules nDecisions="3" unknownInt="2">    ← вариантов три; если ни одно из правил не подошло, брать последний («ворон»)
    		<rule mod="100" min="10" max="20" decide="2" />  ← остаток на 100 от 10 до 20 — «ворон»
    		<rule mod="10" min="1" max="1" decide="0" />
    		<rule mod="10" min="2" max="4" decide="1" />
    	</pluralRules>
    Ответ написан
    Комментировать
  • Почему рекурсивные алгоритмы работают медленнее своих линейных аналогов?

    @Mercury13
    Программист на «си с крестами» и не только
    Потому что этот алгоритм, будучи рализован в лоб без всяких кэшей и ускорителей, неоптимален! F(n–2) высчитывается дважды, F(n–3) трижды, F(n–4) пять раз, и т.д. по Фибоначчи.

    Что читать, сказать не могу (в том издании Кормена, что у меня, маловато), гугли «динамическое программирование». Хотя поначалу поможет и Кормен.

    ЗЫ. В некоторых случаях помогает вычисление в лоб, но с простеньким кэшем, который снизит повторяемость если не для всех входов, то хоть для самых вопиющих.

    ЗЗЫ. Программисты не любят рекурсию по многим причинам. 1. Сложно наладить аварийный стоп. 2. Системный стек ограничен и есть риск переполнения.
    Ответ написан
    4 комментария
  • Как реализовать расписание кругового турнира?

    @Mercury13
    Программист на «си с крестами» и не только
    Других методов я просто не вижу. Искать «round-robin tournament algorithm». Но, по-моему, проще поворачивать не кольцо, а схему матчей, поставив одну из команд (лучше последнюю) в центр круга.
    https://commons.wikimedia.org/wiki/File:Round-robi...
    Round-robin-schedule-span-diagram.svg
    Матчам 2-13, 3-12 и т.д. даём фиксированные направления, причём попеременно: 2-13, 12-3, 11-4…
    Направление матча 1-14 постоянно меняется.

    Если команды нумеруются от 0 до N−1, N — чётное, алгоритм получается такой.
    trans := случайная перестановка чисел 0 … N−1
    
    если N чётное
      то M := N−1
      иначе M := N
    
    цикл 2half = false…true
      цикл round = 0…M−1
        цикл shift = 1…[M/2]
          home := (round + shift) % M
          away := (round + M − shift) % M
          если (shift нечётное) xor 2half
            обменять home, away
          вывести trans[home], trans[away]
    
        если N чётное
          home := round
          away := M
          если (round нечётное) xor 2half
            обменять home, away
          вывести trans[home], trans[away]

    Для чётного N одна команда постоянно меняет поле, у остальных — единожды на круге сбивается. Для нечётного N ничего не сбивается.
    Ответ написан
    2 комментария
  • Поддержание максимума в окне?

    @Mercury13
    Программист на «си с крестами» и не только
    Порядок, придумал.

    Создадим список максимальной длины w, в котором содержатся пары (index, value) и value нестрого убывает. Как устроен этот список — разберём позже.

    Сначала список пуст. Сначала w раз проводим операцию «a[i] вошёл». Затем n−w раз — сначала «a[i−w] вышел», затем «а[i] вошёл». Каждый раз list.front — это индекс и значение макс. элемента.

    Операция «a[i] вошёл». С конца списка удаляем все элементы, чей value меньше, чем a[i]. Затем прицепляем пару (i, a[i]) к концу.
    Операция «a[i] вышел». Если в начале списка index=i, удаляем первый элемент. Иначе — ничего не делаем.

    Почему O(n)? Единственное, где сложность неочевидна — удаление в операции «a[i] вошёл». Но удалений будет не больше, чем вставок, а их точно O(n) — так что никаких проблем.

    Ну и на сладкое — как должен быть устроен список.
    — Ограниченная длина (не более w).
    — Удаление из начала.
    — Вставка в конец.
    — Удаление из конца.

    Переберите знакомые структуры данных. Какая больше всего подходит? По-моему, кольцевая очередь.
    Ответ написан