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

    @Mercury13
    Программист на «си с крестами» и не только
    В общем случае да. Но иногда подобные оценки можно упростить. И упрощения можно делать по двум статьям:
    1. Более точные оценки. В данном случае не могу придумать.
    2. Более простые оценки: ведь оценка — это символ Ландау O(f(·)), который с точностью до константы. Если, например, K<N, а L<M, то сложность будет просто N·M.
    Ответ написан
    Комментировать
  • Как нарисовать дерево имея его ребра?

    @Mercury13
    Программист на «си с крестами» и не только
    Простите, что запоздало вклиниваюсь.
    Я как-то отвечал насчёт того, как сделать непересекающееся дерево.
    Как построить произвольное дерево?
    Ответ написан
    1 комментарий
  • Как узнать время выполнения сортировки в C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Используйте либо time.h из Си, либо std::chrono из Си++11. Вот пример по второму.
    #include <iostream>
    #include <chrono>
    
    int main()
    {
        using Time = std::chrono::time_point<std::chrono::high_resolution_clock>;
        using Diff = std::chrono::milliseconds;
    
        Time t1 = std::chrono::high_resolution_clock::now();
        int i;
        std::cin >> i;
        Time t2 = std::chrono::high_resolution_clock::now();
        Diff diff = std::chrono::duration_cast<Diff>(t2 - t1);
        std::cout << diff.count() << " ms" << std::endl;
        return 0;
    }
    Ответ написан
    Комментировать
  • Объединение пересекающихся периодов?

    @Mercury13
    Программист на «си с крестами» и не только
    отсортировать массив по start
    текКонец := −∞   // или, например, ⌀ или 0000-01-01
    текИндекс := −1
    для v : массив
      если v.start > текКонец
        текИндекс += 1
      результат[текИндекс].приклеить(v)
      текКонец := макс(текКонец, v.end)


    Если же нужно объединять прямо на ходу…
    отсортировать массив по start
    текКонец := −∞   // или, например, ⌀ или 0000-01-01
    текИндекс := −1
    для v : массив
      если v.start > текКонец
        текИндекс += 1
        результат[текИндекс] := v
      иначе
        результат[текИндекс] := объединить(результат[текИндекс], v)
      текКонец := макс(текКонец, v.end)


    Разумеется, я тут опустил вопросы, связанные с датами-строками и их сравнением. Но вам их придётся решить.
    Изначально считается, что везде end > start. Если не так — данные некорректные и с этим придётся что-то делать.
    Также считается, что массив «результат» ассоциативный (как в PHP).
    Ответ написан
  • Как решить данную задачу?

    @Mercury13
    Программист на «си с крестами» и не только
    Задача сложна и требует серьёзного исследования. Дело тут в том, что второй эшелон — склад промежуточного хранения — добавляют, если затраты НЕлинейны и в больших объёмах развоз товара дешевле. Это, конечно, можно сымитировать более дешёвым завозом на склады. (А также чтобы разгрузить «мёртвые запасы» магазинов и законом больших чисел скомпенсировать случайные колебания спроса, но это, как я понял, не ваше дело.)

    У вас написано: «Стоимость доставки это расстояния между точками». Таким образом, стоимость доставки не зависит от объёма подвоза, а зависит только от расстояния? Тоже, так сказать, нелинейное поведение. Приближённое решение ищите в алгоритмах кластеризации: зона, охватываемая одним складом — и есть кластер. Если же нужно точнее, приходится использовать какие-нибудь эвристики.

    Например, расположим семь складов в каких-нибудь городах, вычислим вытекающую стоимость. А теперь вопрос: можно ли перенести какой-нибудь склад в соседний город, чтобы было дешевле? Итерационно работаем, пока не уляжемся в «оптимальное положение». Случайно набрасываем семь точек — и «скатываемся вниз», пока не успокоимся, и так много-много раз.

    Лучше выводить не одно решение, а несколько — например, до 120% от оптимального. Дело в том, что после компьютерного вычисления возникнут какие-то человеческие факторы: узнав, что восточный склад только в Комсомольске-на-Амуре, а северо-западный можно ставить где угодно в окрестностях Ленинграда, принимаем решение ставить склады в Комсомольске и недалеко от ленинградского метро.
    Ответ написан
    Комментировать
  • Как реализовать приближенный двоичный поиск?

    @Mercury13
    Программист на «си с крестами» и не только
    Алгоритм такой.
    1. Реализуй двоичный поиск с указанием места, куда вставить (см. Википедию).
    2. Если вставить до первой позиции или за последнюю — return соотв. первое или последнее число.
    3. В противном случае выяснить, какое ближе из (i−1)-го и i-го.

    Дома проверю, где там ошибка в двоичном поиске, в нём любят ошибаться.

    К тому же код у вас чисто олимпиадный, я бы отделил рабочую логику от генерации выходного файла.
    Ответ написан
  • Как найти среднее Hue (или другую «закольцованную» величину)?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    Пока самый удачный способ таков.

    Переходим в координаты (x, y): x = cos(hue) · sat, y = sin(hue) · sat.
    Там можно получить среднее (x, y) и взять atan2.
    Ответ написан
    Комментировать
  • Как быстро проверить правильная ли скобочная последовательность?

    @Mercury13
    Программист на «си с крестами» и не только
    Критерий таков.
    1. Баланс ( и ) должен сохраниться.
    2. а) Либо вырожденный случай (скобку меняем дважды на противоположную);
    б) либо ) → ( идёт перед ( → );
    в) либо уровень вложенности обеих скобок не менее 3, при этом они сидят в общих скобках 2-го уровня.

    Из-за 2в без предобработки за O(n) проверить невозможно. Но предобработка хороша, если мы решаем кучу таких запросов, и с этим отдельный вопрос. В таком случае, вероятно, лучше использовать дерево отрезков, обрабатывающее каждый запрос за log n. Пока мне алгоритм понятен не до конца; если вы скажете, что действительно задача про кучу запросов — подумаю..
    Ответ написан
    Комментировать
  • Какую функцию использовать для построения такого графика?

    @Mercury13
    Программист на «си с крестами» и не только
    Вариантов много. Считаем пока, что никаких требований к f(0) нет. Перемасштабируем наши переменные:
    x' = (x − 1)/9
    y = 8y' + 2.
    Тогда x' и y' будут от 0 до 1, в то время как x и y — 1…10 и 2…10. И тогда варианты.
    1. Степенная функция: y' = x'a, a = 0…1. Если a=½, то квадратный корень, ⅓ — кубический корень…
    2. Четвертушка эллипса: y' = sqrt(1 − x'²)
    3. Синус: y' = sin((pi/2)·x')

    Если же f(0) = 0, то масштабируем по-другому:
    x' = x/10
    y = 10y'
    Ответ написан
    4 комментария
  • Структура данных типа очереди, позволяющая быстро определить позицию элемента. Есть?

    @Mercury13
    Программист на «си с крестами» и не только
    Очередь, физически не перемещающая элементы (кольцевая или некое подобие std::deque) и позволяющая по указателю быстро определить позицию.

    Плюс любой индекс (хэш- или деревянный), элементы которого — указатели на элементы очереди. Если элементы очереди unique_ptr, то указатели будут именно на unique_ptr! Для «мусорных» языков (C#, Java) вместо указателей можно придумать какие-то идентификационные коды — например, сочетание из номера в пуле массивов и номера в массиве.

    Enqueue — вписываем элемент в индекс. Dequeue — удаляем из индекса. Обмен позициями — корректируем эти два элемента индекса.
    Ответ написан
    Комментировать
  • Как получить доступ к полям и методам неизвестного объекта?

    @Mercury13
    Программист на «си с крестами» и не только
    Это значит: объект класса B должен иметь (или не иметь) «товарища» класса C. Иметь не во владении, а по ссылке. А лучше не класса C, а его подмножества, реализованного как интерфейс (я его назвал IC). На A вообще чхаем — его задача собрать B и C в нужном виде, и всё.

    Смотрите шаблон проектирования Dependency Injection.

    class IC {   // interface
    public:
      virtual int getC() = 0;
      virtual ~IC() = default;
    };
    
    class C : public IC {
    public:
      int getC() final { return 42; }
    };
    
    class B {
    public:
      IC* buddy() const { return fBuddy; }
      void setBuddy(IC* aBuddy) { fBuddy = aBuddy; }
      void someJob() const { if (fBuddy) std::cout << fBuddy->getC() << std::endl; }
    private:
      IC* fBuddy = nullptr;
    };
    
    class A {
    public:
      A() { b.setBuddy(&c); }
    private:
      C c;
      B b;
    }

    Специально для тех, кто на Си++: в таком виде A неперемещаем из-за указателя c.fBuddy. Есть много способов исправить это — например, подправить конструктор копирования и операцию «присвоить» класса A.
    Ответ написан
    3 комментария
  • Как востановить бинарное дерево?

    @Mercury13
    Программист на «си с крестами» и не только
    Если одним префиксным/инфиксным/постфиксным — то, разумеется, нет. А если двумя — дело уже интереснее (при условии, разумеется, что все узлы разные).
    Ну, например, как восстановить дерево, которое было пройдено сначала префиксно, потом постфиксно (самый сложный и интересный случай). Признаюсь сразу: полное восстановление невозможно, ведь ситуации «без левых сыновей» и «без правых сыновей» различить нельзя.

    Если длины не совпадают, СТОП: некорректные данные.
    В префиксном обходе корень в начале, в постфиксном — в конце. Если они не одинаковы, СТОП: некорректные данные.
    Если в обходе один элемент — с этим всё понятно.
    Второй элемент префиксного обхода — левый сын. Ищем его в постфиксном обходе. Если он предпоследний — перед нами та самая ситуация «у дерева один сын», и рекурсивно запускаем алгоритм на обходах без корня.
    В противном случае выкусываем подстроки нужной длины (реально или виртуально), дважды запускаем алгоритм рекурсивно.

    Пример: у нас дерево.
         a
      b    c
    d  e   f

    Префиксный обход abdecf, постфиксный debfca. Корень a, левый сын b, он в постфиксном обходе на третьей позиции. Рекурсивно запускаем алгоритм на парах bde/deb и cf/fc.
    Ответ написан
    Комментировать
  • Алгоритм обхода площади за минимальное время?

    @Mercury13
    Программист на «си с крестами» и не только
    Если поворот «бесплатен», эквивалентны все несамопересекающиеся маршруты.
    Если нет — точного доказательства не вижу, но, по-видимому, тоже оптимальна (2h−2 поворота).

    UPD1. Если из-за каких-то особенностей прохождения поворотов на скорости нежелательны развороты на 180° — тогда спираль с первым отрезком по длинной стороне.
    Ответ написан
  • Режим работы из массива в одну строку?

    @Mercury13
    Программист на «си с крестами» и не только
    Скажем прямо: данная конструкция работает только если выходные дни в начале или конце недели. Если в середине — придётся расширять алгоритмы, да и труднолокализуема (языковые строки одновременно служат техническими ключами).

    Заводим три переменных: firstDay, lastDay, workHours. Поначалу они пустые строки.
    Проходимся по всем элементам.
    • Если рабочие часы совпадают с workHours — перезаписываем lastDay, и всё.
    • А если нет — проводим функцию dump, перезаписываем все три.
    После цикла принудительно проводим dump.

    Как должна работать функция dump.
    • Если workHours пусто — ничего не делаем.
    • Если результирующая строка непуста — добавляем разделитель.
    • В зависимости от того, совпадают ли firstDay и lastDay, добавляем «Пн» или «Пн-Пт».
    • Ну и, наконец, добавляем рабочие часы.
    Ответ написан
    Комментировать
  • Как создать массив с равноудаленными элементами?

    @Mercury13
    Программист на «си с крестами» и не только
    Неплохие результаты получились с одной из первых версий с простейшим патчем на повторы.
    #include <iostream>
    
    struct Sector
    {
        char name;
        int total, counter, remainder;
        bool allowNearby;
    };
    
    enum { N = 3 };
    Sector secs[] { { 'A', 3 }, { 'B', 6 }, { 'C', 30 } };
    
    int main()
    {
        int nTotal = 0;
        for (int i = 0; i < N; ++i) {
            Sector& sec = secs[i];
            nTotal += sec.total;
        }
        for (int i = 0; i < N; ++i) {
            Sector& sec = secs[i];
            sec.counter = sec.total;
            sec.remainder = sec.total;
            sec.allowNearby = (sec.total * 2 > nTotal);
        }
    
        Sector* repeatS = nullptr;
        for (int iSec = 0; iSec < nTotal; ++iSec) {
            Sector* bestS = nullptr;
            for (int i = 0; i < N; ++i) {
                Sector& sec = secs[i];
                if (sec.remainder != 0 && &sec != repeatS) {
                    if (!bestS) {   // первый подходящий?
                        bestS = &sec;
                    } else {    // лучше bestS?
                        if (sec.counter < bestS->counter
                                || (sec.counter == bestS->counter && sec.total > bestS->total))
                            bestS = &sec;
                    }
                }
            }
            if (!bestS)     // так и не нашли, что брать — берём repeatS
                bestS = repeatS;
    
            // пересчитаем счётчик и остаток
            bestS->counter += nTotal * 2;
            --bestS->remainder;
    
            for (int i = 0; i < N; ++i) {
                Sector& sec = secs[i];
                sec.counter -= sec.total * 2;
            }
            repeatS = bestS->allowNearby ? nullptr : bestS;
            std::cout << bestS->name;
        }
        std::cout << std::endl;
    }

    Пишу на «си с крестами». Раз уж вы работаете на PHP, вместо указателей придётся держать «лучший индекс» и «последний индекс».

    Принцип программы прост. Счётчики считают вниз с разной скоростью; чем чаще встречается сектор, тем быстрее. Приоритет взятия такой: не исчерпан → без повторов → у кого счётчик меньше → какой встречается чаще. Однако если какого-то сектора больше 50%, для него повторы вполне себе разрешены.
    Ответ написан
  • Есть ли алгоритм определения оптимального размера посылки?

    @Mercury13
    Программист на «си с крестами» и не только
    Упаковка параллелепипедов. NP-полная задача, точное решение — перебор. Сам решал упаковку прямоугольников (т.е. в 2D) генетическим алгоритмом с переменным успехом.
    Скорее всего, у вашей службы доставки есть ящики стандартного размера — потому стоило бы приспосабливаться к этим ящикам.
    Ответ написан
    1 комментарий
  • Как найти минимальное время которое удовлетворяет условие?

    @Mercury13
    Программист на «си с крестами» и не только
    Если для боя с боссом нужно 0 очков — выводим 0.
    Если все миссии в сумме дают <K очков — выводим «Нельзя».
    А если нет — ничего пока не вижу, кроме продвинутого перебора.

    Сортируем миссии по соотношению «очки/время». Главное — правильно отсечь перебор: например, каждый раз линейно экстраполируем по соотношению «очки/время» лучшей доступной миссии: удастся вписаться или нет? Смотрим на сумму «хвоста»: можно ли вообще, просуммировав «хвост», получить K?
    int N, K;
    Mission missions[100];
    int bestTime = std::numeric_limits<int>::max();
    
    bool recurse(int firstMission, int currPoints, int currTime)
    {
        if (currTime >= bestTime)
            return false;
        if (currPoints >= K) {
            bestTime = currTime;
            return false;
        }
        if (firstMission >= N)
            return true;
    
        int remPoints = K - currPoints;
    
        for (int i = firstMission; i <= N; ++i) {
            bool isFirst = (i == firstMission);
            const Mission& im = missions[i];
            if (currPoints + im.tailPoints < K)           // tailPoints = сумма очков по хвосту от missions[i] до конца
                return isFirst;
            float wantedTime = currTime + remPoints * im.ratio;    // ratio = (float)m.time / m.points;
            if (wantedTime >= bestTime)
                return isFirst;
            if (recurse(i + 1, currPoints + im.points, currTime + im.time))
                return isFirst;
        }
        return false;
    }

    Миссии, как и раньше, отсортированы по ratio по возрастанию.

    И последняя моя идея, в разы ускорившая перебор, такова. Рекурсивная функция возвращает true, если весь разрешённый «хвост» безнадёжен и в нём искать больше нечего. Это происходит при таких условиях.
    1. Хвост пуст.
    2. Хвоста недостаточно, чтобы получить K очков.
    3. Простейшая экстраполяция по 1-й миссии провалилась.
    4. Уже первый рекурсивный вызов говорит: хвост безнадёжен.

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

    @Mercury13
    Программист на «си с крестами» и не только
    Твоё дело — перебор с кэшированием.
    Для каждой кнопки вручную закидываем в массив конских «соседей» — ни одного для 5-ки, три для 4 и 6, два для остальных.
    Затем заведи массив 10×7 (стартовая кнопка×длина) и устрой рекурсию с одним небольшим добавлением: если оно закэшировано, брать из кэша. Правила — f(b, 1) = 1, для остальных — sum{c=сосед(b)} f(c, i−1).
    Можно и динамическим программированием, без рекурсии — всё равно расход вычислительной мощи незначительный. Сначала f(b, 2), затем f(b, 3), и т.д. до 7.
    Ответ написан
  • Какая еще бывает логика, не считая ТТЛ?

    @Mercury13
    Программист на «си с крестами» и не только
    По Википедии…

    Резисторно-транзисторная
    Эмиттерно-связанная
    Диодно-транзисторная
    Транзисторно-транзисторная
    Интегрально-инжекционная
    На диодах и транзисторах Шоттки (традиционно и неверно тоже считается ТТЛ)
    n-МОП
    КМОП

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

    Смысл транзистора (в ключевом режиме) — переключением одной цепи мы переключаем другую. Это же можно сделать и на радиолампах, и на реле.

    Копаясь по Википедии, я выяснил, что нелинейные элементы, пригодные для логики, должны обладать такими свойствами.
    • Восстановление логических уровней — если на вход придёт плохой «0» или плохая «1» (но всё же он примет её за 0 или 1), на выходе будет «0» или «1» значительно лучшего качества.
    • Каскадируемость: можно наладить g(f(x)).
    • Fan-in: возможность использовать несколько сигналов одним элементом.
    • Fan-out: выдача сигнала на несколько элементов.
    • Изоляция между входами и выходами.

    Говорят, будущее — оптические компьютеры, но на входах и выходах таких компьютеров один хрен придётся свет преобразовывать в электричество.

    Если что-то сделать, не используя процессоры — к вашим услугам аналоговые вычислительные машины. Без транзисторов и тиристоров в них (электронных, естественно) тоже никуда, но процессора в них нет. А ведь есть и механические АВМ (гуглите, например, ПУАЗО, немало крови попортивший немецким бомбовозам), и гидравлические АВМ (гуглите гидроинтегратор, MONIAC).

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

    @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². Почти уверен, что реально цифра меньше, просто думать над этим облом.
    Ответ написан
    Комментировать