Задать вопрос
  • Как построить топологию сетей (данные в FDB таблице) когда связи замкнуты в кольцо?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вообще говоря, однозначно вы восстановить все не сможете. Допустим у кольцо из 6 узлов:
    100-101-102-103-104-105-100

    При этом можно добавить связи 100-103 и 101-104. А можно добавить 100-104 и 101-103. В обоих случаях вы по всем портам видите одно и то же - все узлы. FDB таблица будет идентична.

    Вы можете только определять компоненты двусвязности в графе - этакие кольца с всеми хордами внутри, но конкретные их формы - вы знать не можете.

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

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Тут есть 2 цели: передать как можно больше информации, сделать график читаемым.

    По первой цели надо выбрать такую шкалу, чтобы как можно больше разных цветов было на графике. Можно, например, максимизировать энтропию. Пусть pi - доля стран цвета i. Надо максимизировать sumi=1..n -log(pi) pi

    По второй цели - надо выбрать красивые круглые границы. Ну не воспримет человек, если у вас граница цевета будет 111 234 564 человек. Ему проще будет 100 000 000. Плюс, произвольные числа на шкале делать не принято. Обычно делают только 2 типа шкал: линейная и логарифмическая. В линейной каждая граница на одно и тоже число больше, в логарифмической в одно и то же число раз. У вас на примере логарифмическая шкала, чуть округленная до круглых чисел. Если тут взять линейную, то почти все страны попадут в белый цвет из-за парочки очень больших стран.
    Ответ написан
    2 комментария
  • Как найти площадь большого сегмента?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    горизонтальной прямой, проходящей на заданном расстоянии С от соотвествующей оси координат.


    Это прямая y=C. Горизонтальная прямая - она всегда на одно и то же расстояние выше оси OX. Вот это расстояние - C - вам и дано. Ясно, что "соответствующая" ось координат - это OX. потому что с OY горизонтальная прямая пересекается. И вообще, расстояние между двумя прямыми имеет смысл, если они параллельны.

    Легче сначала решить другую задачу: Найдите нужную площадь, если расстояние от прямой до центра окружности - D. Уже потом подставьте в это abs(C-Y0).
    Ответ написан
  • Какие переходы для ДП у "Гелифиш и незабудка" codeforce?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Не вижу, где тут ДП.

    Вы правильно заметили, что можно вместо ai и bi рассматривать только ai^bi. Мы как будто берем все A по дефолту и каждый игрок может своим решением это стандартное действие отменить, что равносильно брать или не брать ai^bi.

    Но надо заметить еще одно важное свойство (пусть di = ai ^ bi). Любое di можно заменть на di^dj (j > i) и задача остается эквивалентной. Тут каждый игрок вместо решения брать или не брать dj в зависимсоти от текущей суммы, он будет решать. делать ли статус у dj таким же как у di или нет. Ну или, рассуждение как выше, стандартное действие - брать dj если di было взято. Каждый игрок может своим решением это действие отменить.

    А это уже очень похоже на вычитание линейных уравнений друг из друга в методе гауса. Можно этим заняться и привести Di к виду, где для каждого разряда мы найдем "базисный вектор" и единица будет стоять только в нем, а во всех остальных числах на этом разряде будет стоять 0. Для каких-то разядов мы может такого не найдем, там может быть много единиц, но все они будут в базисных векторах для каких-то других разрядов. При чем начинайте фиксировать базис с максимальных разрядов. Тогда у вас будут какие-то базисные разряды, и не базисные, которые однозначно определяются базисными и меньше их.

    Т.е. идете с конца по массиву D, из текущего числа xor-ите все базисные вектора. если стоит единица в нужном разряде. Если там остался не ноль, добавляете это число в базис для старшего бита.

    И тут уже сразу ясно, надо ли какое-то Di брать или нет. Каждый игрок знает, хочет ли он этот разряд в конце сделать 1 или 0 (в зависимости от его цели и того, стоит ли 1 в xor всех A в этом разряде).

    Вот и все. Никакого ДП.
    Ответ написан
    Комментировать
  • Почему неправильно работает Keeloq?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Вот код на C:
    #define KeeLoq_NLF		0x3A5C742E
    #define bit(x,n)		(((x)>>(n))&1)
    #define g5(x,a,b,c,d,e)	(bit(x,a)+bit(x,b)*2+bit(x,c)*4+bit(x,d)*8+bit(x,e)*16)
    
    u32	KeeLoq_Encrypt (const u32 data, const u64 key)
    {
    	u32					x = data, r;
    	
    	for (r = 0; r < 528; r++)
    	{
    		x = (x>>1)^((bit(x,0)^bit(x,16)^(u32)bit(key,r&63)^bit(KeeLoq_NLF,g5(x,1,9,20,26,31)))<<31);
    	}
    	return x;
    }
    
    u32	KeeLoq_Decrypt (const u32 data, const u64 key)
    {
    	u32					x = data, r;
    	
    	for (r = 0; r < 528; r++)
    	{
    		x = (x<<1)^bit(x,31)^bit(x,15)^(u32)bit(key,(15-r)&63)^bit(KeeLoq_NLF,g5(x,0,8,19,25,30));
    	}
    	return x;
    }


    Ищите несоответствия. Во-первых, надо делать не &64, а &63.

    Во-вторых, в encrypt надо использовать x, а не data. Чтобы каждый следующий раунд использовал результат вычисления предыдущего, а не считал заново одно и то же.
    Ответ написан
  • Какие переходы для ДП Codeforces Петя и пауки?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Сначала переформулируем задачу - надо найти минимальное количество клеток с пауками в конце. Каждая клетка доски должна быть "покрыта": или быть с пауками, или быть соседней с клеткой с пауками.

    У задачи много решений.
    Во-первых, можно порисовать частные случаи с маленькими досками и найти формулу.
    Для мелких значений там частные случаи, а для больших можно получить формулу.

    Что-то вроде такого расположения работает на больших досках:
    *...*...*
    ..*...*..


    Во-вторых, можно решать через графы. Пусть каждая клетка - вершина графа. Ребра между соседними клетками. Заметим, что граф - двудольный (шахматная раскраска доски - это и есть 2 доли). Исходная задача поиска минимального количества клеток с пауками, это фактически задача поиска контролирующего множества вершин: каждая клетка должна или быть во множестве, или быть соседней с клеткой из множества. В двудольном графе эта задача равнасильна поиску максимального паросочетания. Это решение за O(n^2m^2).

    Если хотите ДП, то перед переходами надо определиться с состояниями. Ясно, что будет какое-то ДП по профилю. Будем целиком покрывать сколько-то первых строк, а состояние последней строки хранить в маске. Поскольку каждая клетка может быть покрыта как снизу, так и сверху, то в состоянии надо держать состояние 2 последних строк.
    Например, состоянием может быть f(k,m1,m2) - расставили пауков в клетках их первых k строк. Считаем минимальное количество пауков. Первые k-1 целиком покрыты, строка k покрыта маской m1, а строка k+1 - маской m2. База f(0,0,0) = 0, f(0, m1, m2) = infinity. Ответ будет в min_m2 (f(n, 2**m-1, m2)).

    Переход снизу вверх: перебираем, куда мы ставим пауков в строке k+1. При этом важно, что бы строка k оказалось целиком покрыта. Пресчитываем маску в строке k+1, а маска в строке k+2 - это будет куда мы поставили пауков:
    F(k,m1,m2) --> F(k+1, m2 | m3 | (m3 << 1) | (m3 >> 1), m3), для всех m3, таких что m3 | m = 2**m-1

    Это будет решение за O(n6^m).
    Ответ написан
    Комментировать
  • Как правильно заниматься перебором: a³ + b³ + c³ = d³?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Циклы в питоне - это ужасно медленно, да. Не лучший язык для написания числодробилки.

    Еще, оффтопик, база даных тут совсем не нужна. Достаточно выводить числа на экран или в текстовый файл. так быстрее будет.

    У вас самый наивный перебор в лоб, его можно ускорить. Сначала сгенерируйте все кубы и схраните их в массив. Их будет примерно кубический корень из |MAX_VAL-MIN_VAL| - это достаточно маленькая величина.

    Теперь задача: найти 3 числа в массиве, дающих в сумме число из массива. Это все еще O(n^3), если исползовать 4 индекса. Но можно ускорить решение методом "встреча по середине".
    Вместо A+B+C=D из массива будем искать A+B=C-D. Для этого переберем все пары чисел, подсчитаем их сумму и сохраним в dict() вместе со списком индексами этих чисел (список всех пар, дающих эту сумму). Потом опять переберем все пары, подсчитаем их разность и посмотрим в словаре, а была ли пара с такой же суммой. Если была - вот мы нашли 4 числа, таких что A+B=C-D. извлекаем корни и выдаем это в ответ.
    Это будет уже O(n^2) - заметно быстрее.
    Ответ написан
    8 комментариев
  • Какую букву в игре поле чудес в этом случае лучше всего открыть? правильное ли это решение?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    В поле чудес открываются все вхождения названной буквы. Поэтому надо называть ту, которая отбросит как можно больше слов.

    Так в вашем примере буква О откроется либо нигде, либо в 3-ей позиции, либо в 3-ей и 5-ой. В любому случае отбросятся 2 из 3 слов.

    В примере
    СЛЕВА
    СЛОВА
    СЛОВЕ

    Надо назвать E. Либо откроется 3-я буква, либо никакая, либо 5-ая. Опять, с одной попытки вы уменьшили множество вариантов до 1.

    Надо все слова сгруппировать по тому, на каких позициях в них стоит данная буква (пустое множество позиций - отдельный вариант). Из всех групп вас интересует худший вариант - самая большая. Вам надо выбрать букву, чтобы самая большая группа была минимальна.
    Ответ написан
    2 комментария
  • Почему моя реализация Shaker Sort-а такая медленная?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Возможно, копирование данных во временные переменные a и b - медленно. Сравнивайте просто item[i] и item[i+1].

    Потом, попробуйте закоментировать второй цикл, который идет с конца в начало. Сортировка первратится в bubbleSort. Я думаю, что это здорово ускорит ее. Тут дело в железе: самое медленное в современных процессорах - это обращение к памяти. И оно всякими костылями и подпорками ускореяется. Один из таких костелей - перефетчинг: когда вы читаете данные из какого-то места, процессор заранее читает сразу блок побольше, вдруг пригодится. Начиная с этого адреса и вперед.
    И когда цикл идет i++, то эта оптимизация срабатывает. Данные для следующей итерации уже оказываются в кеше и работа с ними быстрая. С i-- этот фокус никак не помогает, поэтому циклы задом-на-перед гораздо медленнее циклов идущих по возрастанию индексов, даже если там ровно такие же операции. Потому что там команда чтения данных занимает гораздо больше тактов процессора.
    Ответ написан
    5 комментариев
  • Корректно ли вызывать метод у временного объекта?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    DeepSeek галлюционирует. Временные объекты живут до конца выражения. unique_ptr начнет уничтожение после выполнения всего выражения.

    Вот ссылки на стандарт:

    make_unique<> является prvalue: https://en.cppreference.com/w/cpp/language/value_c...
    prvalue: a function call or an overloaded operator expression, whose return type is non-reference


    В момент вызова происходит материализация временного объекта: https://en.cppreference.com/w/cpp/language/lifetime

    Temporary objects are created ... in the following situations:
    when performing member access on a class prvalue.


    Там же написано:
    All temporary objects are destroyed as the last step in evaluating the full-expression


    Т.е. возвращенный make_unique объект будет уничтожен только в конце строки.
    Ответ написан
    Комментировать
  • Как симулировать комбинаторные сочетания (C(k, n)) за O(1) памяти?

    wataru
    @wataru Куратор тега Математика
    Разработчик на С++, экс-олимпиадник.
    Вам не нужно их симулировать. Вам нужно их подсчитать. Есть же формулы, на той же википедии посмотрите. Через факториалы. Считается это несколькими циклами прямо в одной переменной. Только аккуратно с большими числами, могут быть переполнения, если у вас нет длинной арифметики в языке.

    Edit: Если, все-таки, хочется именно случайно сочетание сгенерировать, то вот алгоритм, основанный на reservoir sampling:

    int taken = 0;
    for (int i = 0; i < n; ++i) {
      if (rand()*(n-i) < k-taken) {
        ++taken;
        // Взять элемент i.
      }
    }
    Ответ написан
  • Как лучше восстановить индексы в n-мерном рюкзаке с точным весом?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Для восстановления вам нужен второй массив, где вы храните одно из 3 значений - в какой из трех наборов идет i-ая гиря. Когда считаете dp и пишите, что можно составить i, w1, w2 вы же эти значения получили куда-то поместив последний элемент. У вас или к w1 что-то было прибавлено, или к w2, или никуда. Это 3 варианта.

    При восстановлении начните с N, sum/3, sum/3 и в цикле вычитайте 1 из первого индекса и вес i-й гири или не вычитайте или вычитайте из одного из весов, в зависимости от сохраненного знаяения во втором массиве.

    Можно вместо второго массива в dp хранить 0, если это состояние невозможно и или номер кучки для последней гири.
    Ответ написан
    Комментировать
  • Когда передавать копию callable, а когда через rvalue reference?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    В дизайне интерфейса можно передавать и копию и rvalue refernce. Что выбрать - решать вам, как создателю интерфейса. Если у вас предполагаются использование функций/лямбд без тяжелого состояния (связанные аргументы по значению), то копирование дешевое и не надо его вводить. rvalue reference всегда дешево, но у вас нельзя будет переиспользовать предикат между разными вызовами.

    В функциях из algorithm используется передача по значению, что бы не ограничивать пользователя. Вы можете один раз написать нужный вам предикат и передавать его во все вызовы. Плюс исторически так сложилось. Когда оно все созадавалось, никаких rvalue reference и не было. Потом их добавлять и не стали, потому что в самом частом случае, если лямбду прямо в аргументах задавать, то она практически бесплатно передастся.

    Там не используется передача по ссылке (lvalue reference), потому что это не так универсально в шаблоне. Если у вас какой-то const предикат, вы его в reference не передадите. А делать const & в интерфейсе нельзя, вдруг у вас предикат не константный?

    И, если вы все-таки хотите передать ссылку, то можно передать std::ref(pred), тогда в шаблоне выведется передача по ссылке.
    Ответ написан
    Комментировать
  • Как научиться решать алгоритмические задачи?

    wataru
    @wataru Куратор тега Алгоритмы
    Разработчик на С++, экс-олимпиадник.
    Тут поможет только практика. Чтобы научиться решать задачи - надо много задач решить. Если не можете какую-то задачу решить сами, читайте разбор или чужие решения, пока не поймете, что там происходит. И потом обязательно реализуйте решение сами, не перепечатывая готовое.

    Хорошие книги: Кнут "искусство програмирования", Кормен "алгоритмы. Построение и анализ", Бхаргава "Грокаем алгоритмы". Старайтесь прорешивать все упражнения в этих книгах. Но прочитав их вы задачи решать не научитесь, а лишь подтянете базу.
    Ответ написан
    Комментировать
  • Правильно понимаю из статьи про умные указатели?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Похоже на ошибку оформления. Это должен быть вызов функции. В тексте выше даже написано про вызов.

    Тут автор считает, что сначала выполнится new A(), потом new B(), потом конструктор unique_ptr. Если исключение бросит B(), то действительно будет утечка памяти. Объект A, полученный через new умрет еще до оборачивания в unique_ptr. Такой сырой указатель автоматически не удалится.

    Такая последовательность невозможна c С++17:
    In a function call, value computations and side effects of the initialization of every parameter are indeterminately sequenced with respect to value computations and side effects of any other parameter.


    Evaluations of A and B are indeterminately sequenced : they may be performed in any order but may not overlap: either A will be complete before B, or B will be complete before A. The order may be the opposite the next time the same expression is evaluated.


    Но до C++17, действительно, компилятор может перемешать вычисления аргументов как угодно.
    Ответ написан
    Комментировать
  • Как отсортировать список из массивов?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Зависит от возможных значений чисел. Если числа не очень большие, то RadixSort будет самым быстрым вариантом. В противном случае самая обычная стандартная сортировка будет достаточно быстрая. Просто надо как-то ей передать функцию сравнения двух массивов, которая смотрит на первый элемент, а потом на второй. Что-то вроде a[0] < b[0] || (a[0] == b[0] && a[1] < b[1]). Не специалист по C#, но возможно массивы уже так и сравниваются там.
    Ответ написан
    Комментировать
  • Код при самостоятельном тестировании работает корректно, а при проверке тестировщиком программа выдает ошибку. В чем может быть проблема?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    У вас неинициализированные переменные mini и maxi. И они используюся такими в сравнениях. Это Undefined Behavior и на разных компьютерах может вести себя по разному. И если вам не повезет и, например, mini окажется каким-то очень маленьким числом, то у вас выдаст не правильный ответ.
    Ответ написан
  • Как из длины массива и максимального количества потоков узнать индексы, которые будет обрабатывать поток?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Вам надо разбить массив на K как можно более равных частей?

    Если длина массива N, то все куски будут длиной хотя бы floor(N/K), и ровно N%K будут иметь на 1 элемент больше. Вроде, если у вас 10 элементов надо на 3 потока разделить, то будут длины {4, 3, 3}. А если 15 на 4, то {4, 4, 4, 3}

    Так что i-ый кусок будет начинаться с позиции (N/K)*i + min(i, N%K) и иметь длину N/K + ((i < N%K) ? 1 : 0).

    Чуть проще формулы, если вы эти позиции явно в массиве получите, а не будете каждую отдельно считать:
    int start[K], end[K];
    int prev = -1;
    for (int i = 0; i < K; ++i) {
      int len = N/K + ((i < N%K) ? 1 : 0);
      start[i] = prev + 1;
      end[i] = start[i] + len;
      prev = end[i];
    }
    Ответ написан
    Комментировать
  • Как называется такая структура данных?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Никак не называется. Можно структуры комбинировать для ваших задач, но это частные случаи применения структур, не настолько частые, чтобы давать этой комбинации какое-то имя. Если уж хочется назвать это как-то, то это все еще будет хеш-таблица с какими-то вспомагательными пристройками для каких-то дополнительных нужных вам свойств (итерация).

    И вообще, у вас тут намудрено, почему нельзя сделать просто:
    let objects: HashMap<Uuid, Object>;

    Тут все такой же O(1) доступ к элементу по id. Зачем вам массив? Вы там добились простой и cache-friendly итерации по всем объектам? Не факт, что это уже не реализовано внутри HashMap. По крайней мере во многих языках можно проитерироваться по всем объектам в стандартной хеш-таблице.

    Зато у вас там удаление элемента - это что-то сложное. Особенно, если вы не хотите избежать фрагментации и неиспользованного места в массиве.
    Ответ написан
    4 комментария
  • Как реализовать алгоритм на С++?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Используйте std::vector. Их можно присваивать как переменные. При этом там под капотом не будет никакого копирования, а просто изменение указателей в данном случае (так как правая часть - временное значение).
    std::vector<int> a, b;
    a = a0;
    b = F(a);
    for (int i = 1; i <= n; ++i) {
      a = G(b);
      b = F(a);
    }


    Если вас напрягает, что функции G и F выделяют память внутри, то можно сделать, чтобы они получали vector, в который надо вернуть значения:
    void F(const vector<int> &a, vector<int>& res);
    void G(const vector<int> &b, vector<int>& res);
    
    std::vector<int> a, b;
    a = a0;
    b = F(a);
    for (int i = 1; i <= n; ++i) {
      G(b, a);
      F(a, b);
    }
    Ответ написан
    Комментировать