51

(7 ответов, оставленных в Algo)

Исправил наконец первую иллюстрацию (всё никак не мог отыскать программу, с помощью которой я их делал: оказалось, это yEd Graph Editor).

Hack-Yer

В этой иллюстрации тоже есть ошибка?

Вроде нет ошибок. Вершине "bc" также соответствует строка "c". Вершине "abcb" также соответствуют её суффиксы "bcb", "cb". Это можно понять с помощью правого рисунка: каждому состоянию соответствует строка, написанная на нём, а также несколько её суффиксов - вплоть до того суффикса, который записан по link'у из этого состояния.

Ну это скорее не ошибка, а нечеткая формулировка. Заменил на вариант википедии, как более ясный и лаконичный.

Спасибо.

53

(7 ответов, оставленных в Algo)

Ошибка в иллюстрации... Состояние для строки "a" не является терминальным.

Спасибо, исправлю.

54

(1 ответов, оставленных в Offtopic)

Спасибо!

(и перенёс тему в offtopic)

55

(1 ответов, оставленных в Feedback)

спасибо.

56

(3 ответов, оставленных в Problems)

В пользу того, что линейного решения нет, говорит такое соображение. Суфф.автомат примерно эквивалентен суфф.дереву, но на суфф.дереве большинство задач осознавать проще. В суфф.дереве задача поиска вершины, соответствующей подстроке [l;r], превращается в поиск такой вершины дерева, которая отстоит на нужном расстоянии от некоторого листа. И эту задачу не видно как решать за линейное время даже в оффлайне.

57

(4 ответов, оставленных в Problems)

ilyaraz пишет:

Ненаправленный случай делается сведением к недвудольному взвешенному паросочетанию.

Правильно ли я понимаю, что это сведение нетривиально, и первым его привёл Эдмондс?
(в 1964 г.; я пытался разобраться по этой статье, но не смог понять, как решать данную задачу)
Также нашлась статья Hartvigsen, но не менее понятная.

58

(3 ответов, оставленных в Problems)

А зачем оффлайн, почему нельзя запомнить состояние автомата для каждого правого конца R?

Про способы без двоичного подъема не слышал.

59

(2 ответов, оставленных в Problems)

(про одуванчик гораздо лучше было. наверное, тот шедевр уже не превзойти...)

60

(15 ответов, оставленных в Problems)

Для нескольких строк подойдет другой метод: давайте склеим наши строки A, B, C в одну строку, разделив их разными символами-разделителями: например, A#B@C$. Построим по полученной строке суффиксный автомат. Для каждого состояния этого автомата определим, в каких строках оно встречается. Например, состояние встречается в строке A, если из этого состояния достижим переход по символу #. Аналогично, состояние встречается в строке B, если из него по переходам достижим переход по символу @ (но при условии, что при проверке достижимости мы не переходим по символу #). Аналогично, состояние встречается в строке C, если из него достижим переход по символу $ (при этом мы аналогично игнорируем переходы по символа # и @).

Посчитав теперь эти достижимости (что можно делать за линейное время - с помощью динамического программирования с меморизацией или оформив это в виде обхода в глубину), мы просто выбираем в качестве ответа такое состояние, которое лежит во всех трёх строках, и при этом имеет наибольшую длину length. (Чтобы вывести саму искомую подстроку, а не только её длину, вместе с каждым состоянием автомата надо будет хранить позицию endpos - она упоминается в статье.)

61

(3 ответов, оставленных в Algo)

Нет, при n=6, k=3 подзадача будет вызываться от n=4, поэтому будет возвращать в отрезке [0;3]. После нормализации в строках 5-8 res=0 и res=1 останутся без изменений, а res=2 и res=3 увеличатся на 1.

Насчет случая 2а - да, пожалуй, технически он необходим, хотя я всё же верю, что эти два случая можно объединить в одну формулу.

62

(3 ответов, оставленных в Algo)

да, в строках 4-5 мы "убиваем" всех k-ых бойцов и вызываемся от меньшей подзадачи размера n-cnt.
однако результат res вызова подзадачи ещё нужно исправить:
1) после экзекуции cnt бойцов мы на самом деле остановились на (k*cnt-1)-ом бойце, а не на 0-ом.
скажем, если бы n%k==0, то мы бы действительно следующим шагом попали в 0-го бойца.
но во всех остальных случаях, т.е. когда n%k>0, результат res надо уменьшить на n%k - именно на столько надо "прокрутить" номера бойцов.
2) после этого исправления нумерации у нас два принципиальных случая:
2а) если res<0, то это означает, что ответом является боец, первоначально находившийся в числе последних n%k человек. Тогда ответ на задачу - это res+n
2б) в противном случае, нам надо сдвинуть res на сколько-то, потому что мы должны учесть номера убитых cnt бойцов - для этого мы прибавляем res / (k-1) - именно столько k-ых бойцов оказалось раньше res-го.

P.S. Мне сейчас кажется на самом деле, что вариант 2б) на самом деле покрывает вариант 2a), т.е. можно убрать строки 6 и 7. Но я не уверен в этом.

63

(3 ответов, оставленных в Algo)

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

Компактная реализация сильно использует хитрости массивов/указателей, поэтому без их понимания разобраться в реализации будет непросто.

  • item - это структура данных, представляющая собой одну вершину дерева.

  • pitem - это указатель на структуру item. Такие указатели мы используем, чтобы хранить связи между вершинами: т.е. левый и правый сын каждой вершины - это указатели, указывающие на некоторые другие вершины (или NULL, если не указывает никуда). Да и корень дерева root - тоже указатель, он указывает на вершину-корень. В общем, всё дерево написано на указателях

  • Что вообще такое ссылка в C++. Упрощенное понимание такое: если у функции какой-то параметр является ссылкой, то внутри функции мы можем присваивать этому параметру какое-нибудь значение, и это значение запишется в передаваемую переменную.

  • pitem & - это ссылка на указатель. Это самый сложный для понимания момент. Давайте посмотрим на одно из мест, где это используется: функция split(). Этой функции передаётся дерево t, ключ key, по которому надо разрезать дерево t, а результат этой функции кладётся в параметры l и r. Соответственно, для чего параметры l и r сделаны ссылками на pitem? Например, чтобы мы могли вызвать эту функцию с аргументом l равным t.r, аргументом r равным чему-нибудь ещё, и результат разрезания записался бы в t.r.

Всё равно, конечно, получилось сумбурно и запутанно, но надеюсь, что вы что-нибудь поймете из моего объяснения.

64

(1 ответов, оставленных в Problems)

Нет, сначала s='', мы к ней приписываем букву 'a', т.е. считаем префикс-функцию для строки reverse(s+'a'), потом s='a' и мы к ней приписываем букву 'b', т.е. t=reverse('a'+'b'), потом s='ab', и т.д.

UPD Спасибо!

65

(6 ответов, оставленных в Problems)

Нет, насколько я помню, вполне там можно обойтись без пометок.

Кажется, что незачем передавать в dfs значение, пришедшее "сверху": ведь в таком решении то, что происходит выше текущей вершины, - не особенно-то и важно. Это решение заражает вершины в жадном порядке, каждый раз заражая только действительно необходимую вершину. Поэтому передавать ничего не надо, а просто надо посмотреть: если мы после запуска dfs из всех потомков видим, что один какой-то потомок вернул отрицательное число, превосходящее по модулю все положительные числа из других потомков - то все эти положительные потомки можно мысленно "удалить".

UPD:

Переписал код, теперь работает так: есть массив int h[], если h[v]==-1

Надо не -1, а -k, потому что это важно, на каком именно расстоянии произошло ближайшее снизу заражение.

66

(6 ответов, оставленных в Problems)

Когда заражаешь какую-то клетку, возвращаешь -k. Т.е. возвращаемое значение может быть отрицательным, и надо написать обход так, чтобы с отрицательными числами всё было адекватно тоже.

67

(6 ответов, оставленных в Problems)

Вроде жадно можно. Просто пускаем обход по дереву, как бы идём от листьев вверх, и как только оказывается, что мы в текущей вершине обязаны заражать, то заражаем. Т.е. делаем dfs, который возвращает расстояние до самого далёкого незаражённого потомка, и если в текущей вершине это значение получилось равно k - заражаем её, иначе - нет.

68

(6 ответов, оставленных в Feedback)

fixed

А как её решать принципом включений-исключений?

Вот вертикальной декомпозицией за N^3 log N понятно как решать: мы сводим трёхмерную задачу к O(N) двумерным задачам (перебрав Z среди имеющихся в инпуте), а каждую двумерную задачу с прямоугольниками сводим к O(N) одномерным (перебрав Y среди имеющихся в инпуте), а каждую одномерную задачу - решив за O(N log N) (сортировкой).

Наконец руки дошли исправить статью.
Спасибо за то, что расставили все точки над i! smile

71

(2 ответов, оставленных в Algo)

Почему нельзя сделать такую простую динамику: d[v][c] - ответ для поддерева с корнем в v, если саму вершину мы покрасили в c (c=0..2).

Считается она так: пусть мы хотим посчитать ответ d[v][c]. Тогда каждый сын вершины v должен иметь цвет, отличный от c. Поскольку разные сыновья не влияют друг на друга, то можно просто для каждого сына to посмотреть значения динамик d[to][(c+1)%3] и d[to][(c+2)%3] и выбрать из них максимальное. Просуммировав это по всем сыновьям, и прибавив единицу, если c=0, мы получим искомое d[v][c].

72

(5 ответов, оставленных в Problems)

Если так вы уже пробовали - тогда не знаю. Просмотром кода я ничего тут не увидел.

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

73

(5 ответов, оставленных в Problems)

В код не вчитывался, но мне кажется, что неправильно работать может то, когда вы вызываете insert() от ключа, уже имеющегося в дереве (не проверяя, что такого ключа не было). Скажем, если node->prior получился большим, чем у всех вершин дерева, то эта новая вершина встанет в корень, а остальное дерево просто просплитится на два - и в результате могут появиться две вершины с одинаковым key.

74

(11 ответов, оставленных в Problems)

Я просмотрел бегло, но думаю, что проблема в том, как ты ищешь ind: ведь в дереве могли быть непропушенные флаги rev, а ты просто поднимаешься до корня и суммируешь. Мне кажется, самый простой способ - дважды пройти путь до корня: первый раз вызывая push вдоль всего пути, и лишь второй раз насчитывая ind.

75

(11 ответов, оставленных в Problems)

А в чём проблема, ведь как делать неявным декартовым деревом reverse на отрезке, описано в статье?

Можно даже обойтись без явных поисков минимума: заранее перенумеровать все числа, и для каждого числа запомнить указатель на его вершину в дереве. Тогда на i-ом шаге мы должны взять вершину, соответствующую числу i, найти её номер в массиве (это делается так: поднимаемся от вершины по предкам до корня, прибавляя каждый раз всё, что оставляем слева от себя), ну и потом просто сделать reverse на соответствующем префиксе (а само число i удалить из дерева вообще, зачем оно нам потом нужно).