Разборчивые строки
От: Serge  
Дата: 07.07.18 13:10
Оценка: 5 (1)
Всем привет.

Есть такая задача: требуется найти количество строк длины n (n <= 100), которые не содержат в себе тандемных повторов длины от 1 до k (1 <= k <= 9) над заданным алфавитом из а букв (1 <= a <= 26). Тандемным повтором называется строка вида ww, где w — это некоторая последовательность букв (в этой последовательности от 1 до k букв).

Будут идеи?
Re: Разборчивые строки
От: zubr Россия  
Дата: 07.07.18 14:39
Оценка:
Здравствуйте, Serge, Вы писали:

S>Всем привет.


S>Есть такая задача: требуется найти количество строк длины n (n <= 100), которые не содержат в себе тандемных повторов длины от 1 до k (1 <= k <= 9) над заданным алфавитом из а букв (1 <= a <= 26). Тандемным повтором называется строка вида ww, где w — это некоторая последовательность букв (в этой последовательности от 1 до k букв).


S>Будут идеи?


Решать в обратную сторону — поставить задачу сколько строк с подстрокой тандемным повтором можно сделать. А дальше найти разницу.
Дело в том что количество плохих строчек оценить несколько проще:

CountRepeating(len, k) = CountNonRepeating(len — 2, k — 2) * Z * (len — 2) + CountNonRepeating(len — 4, k — 4) * (Z * Z) * (len — 4) + ...
CountNonRepeating(len, k) = Z * Z * ... Z — CountRepeating(len, k) — CountRepeating(len, k — 1) — ...

Наверное как то так, хотя вероятно нужно еще доказать достаточность такого условия.

Решение выше относится к случаю когда тандемный повтор есть кусок подстроки: a0, ... ai, w0, ... wk, w0, ... wk, ai+1, ... an
Отредактировано 07.07.2018 14:41 zubr . Предыдущая версия .
Re: Разборчивые строки
От: Кодт Россия  
Дата: 09.07.18 14:32
Оценка:
Здравствуйте, Serge, Вы писали:

S>Есть такая задача: требуется найти количество строк длины n (n <= 100), которые не содержат в себе тандемных повторов длины от 1 до k (1 <= k <= 9) над заданным алфавитом из а букв (1 <= a <= 26). Тандемным повтором называется строка вида ww, где w — это некоторая последовательность букв (в этой последовательности от 1 до k букв).


S>Будут идеи?


Будем количество строк с повторами как функцию от n: R(n).
Всего строк длины n: U(n) = a**n.
Количество строк без повторов, соответственно: Q(n) = U(n)-R(n).

Сразу заметим, что мы получаем полиномы от переменной a, так что можем отложить все вычисления значений, а сперва находить векторы коэффициентов.

Итак. Пусть мы нашли все строки длины до n-1 включительно, и посчитали, сколько там чего.
Попробуем построить строки длины n с повторами.
R(n) = R(n-1) * a     -- дописали произвольную букву к каждой строке с повторами
     + Q(n-1)         -- взяли строку без повторов и удвоили ей хвост длины 1
     + Q(n-2)         -- взяли строку без повторов и удвоили ей хвост длины 2
     ...
     + Q(max(k,n-k))  -- взяли строку без повторов и удвоили ей хвост длины k (если она сама - длиной не меньше k)


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

Например, для хвоста длины 3:
xxxABC + ABC
xxxABCA + CA — откуда B=C, A=C, и на самом деле, xxxAAAA + AA. Понятно, что это строка с повтором, то есть, мы её не удваивали
xxxABCAB + B — откуда A=B, и это xxxAACAA, опять строка с повтором.

Для хвоста длины 4:
xxxABCD + ABCD
xxxABCDAB + AB => A=C, B=D, xxxABAB, строка с повтором
xxxABCDA + CDA, B=C=D=A, строка с повтором

Строго доказывать лень, но КАЖЕТСЯ, что мы действительно имеем дело с непересекающимися классами, поэтому можем суммировать как есть.

Теперь ещё уберём вторую функцию, чтоб получить чисто рекурсивное определение
R(n) = R(n-1) * a
     + a**(n-1) - R(n-1)
     + a**(n-2) - R(n-2)
     ...
     + a**(m)   - R(m), где m = max(k,n-k)


Начальные условия индукции: R(0) = 0 = [0] — далее я буду записывать полиномы векторами коэффициентов, старшими вперёд
Q(0) = [1].
Пустая строка ровно одна, и она, разумеется, без повторов.

R(1) = R(0)*a = [0]a = [0,0].
Q(1) = [1,0]
Единичные строки имеют вид A, и они, конечно, без повторов

R(2) = R(1)*a + Q(1)
= [0, 0, 0] +
  [0, 1, 0]
= [0, 1, 0]
Q(2) = [1,-1,0]

Строки длины 2 с повторами имеют вид AA — понятно, что их ровно a.

R(3) = R(2)*a + Q(2)
= [0, 1,  0, 0] +
  [0, 1, -1, 0]
= [0, 2, -1, 0]
Q(3) = [1,-2,1,0]

Строки длины 3 с повторами имеют вид AAx, ABB — их количество aa + a(a-1) = 2aa-a.

R(4) = R(3)*a + Q(3) + Q(2)
= [0, 2, -1,  0, 0] +
  [0, 1, -2,  1, 0] +
  [0, 0,  1, -1, 0]
= [0, 3, -2,  1, 0]
Q(4) = [1,-3,2,-1,0]

Строки длины 4 имеют вид AAxx, ABBx; ABCC (A≠B, B≠C, A?C), ABAB — их количество aaa + aa(a-1) + a(a-1)(a-1) + a(a-1) = 3aaa-2aa+a.

R(5) = R(4)*a + Q(4) + Q(3) =
[0, 3, -2,  1,  0, 0] +
[0, 1, -3,  2, -1, 0] +
[0, 0,  1, -2,  1, 0] =
[0, 4, -4,  1,  0, 0]
Q(5) = [1,-4,4,-1,0,0]

Строки длины 5 имеют вид AAxxx, ABBxx, ABCCx, ABABx; ABCDD, ABCBC (где C≠A, иначе бы это было удвоение хвоста у строки с повтором ABAB).

R(6) = R(5)*a + Q(5) + Q(4) + Q(3) =
= [0, 4, -4,  1,  0,  0, 0] +
  [0, 1, -4,  4, -1,  0, 0] +
  [0, 0,  1, -3,  2, -1, 0]
= [0, 5, -7,  2, -1, -1, 0]
Q(6) = [1,-5,7,-2,1,1,0]


Ну и так далее.
Заметим следующие вещи:
— коэффициент нулевой степени всегда равен 0, то есть, все ответы кратны a
— для вычисления рекуррентного выражения нам нужны последние k значений — R(n-k),...,R(n-1), то есть, мы можем хранить скользящее окно, а не кешировать всё подряд.

Ещё заметим: начиная с n=2k+1, мы перестаём ветвиться "хватит или не хватит длины в строках без повтора" (условие максимума) и выполняем одну и ту же операцию над нашими полиномами:
R(n) = R(n-1)*[1,0] + R(n-2)*[-1] + ... + R(n-k)*[-1] + [0,1,...,1]*[1]
R(n-1) = R(n-1)
R(n-2) = R(n-2)
....
R(n-k+1) = R(n-k)
[0,1,...,1] = [1,1,...,1]
[1] = [1]

То есть, умножаем вектор полиномов на матрицу M полиномов степени 1. Размер матрицы — (k+2)*(k+2).
                  / [1,0],  -1, -1, ..., -1, -1,     1,  0  \  полином R(n) текущий - считаем по формуле
                  |     1,   0,  0, ...,  0,  0,     0,  0  |  предшествующие - сдвигаем
                  |     0,   1,  0, ...,  0,  0,     0,  0  |
RR(n) = RR(n-1) * <    ...  ... ... ..., ... ...    ... ... >
                  |     0,   0,  0, ...,  1,  0,     0,  0  |  самый последний сдвинули
                  |     0,   0,  0, ...,  0,  0, [1,0],  1  |  полином единичек, нужен для нахождения дополнений
                  \     0,   0,  0, ...,  0,  0,     0,  1  /  константа единичка


Короче говоря,
RR(n) = RR(2k+1) * M**(n-2k-1)

Если целевое значение n очень большое, то мы можем возвести матрицу в соответствующую степень за логарифмическое от n время (но за квадратное от k, разумеется!).
Ну и там повылазят временные коэффициенты (n*k)**2, потому что у нас полиномы будут набухать. Но они и так повылазят, при линейном умножении вектора на матрицу.

Короче говоря, можем выгадать k*k*logn против k*n.
С достаточно хреновым коэффициентом при асимптоте — потому что мы станем жрать память, крутить хитрые циклы, ещё кешмиссы полезут...

Для n<=100 проще не париться, а выполнить линейный забег по формуле.
Перекуём баги на фичи!
Отредактировано 09.07.2018 14:35 Кодт . Предыдущая версия .
Re[2]: Разборчивые строки
От: Кодт Россия  
Дата: 09.07.18 18:17
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Попробуем построить строки длины n с повторами.


Поставил натурный эксперимент на a=3, n=15, k=5. (Тупо проверил все 14 миллионов строк). Получил R=14364157, Q=738
То есть, строк без повторов радикально меньше, чем строк с повторами.

Что, если переписать формулу, посчитать именно дополнения?
Q(n) = U(n) - R(n)
     = U(n) - R(n-1)*a            - Q(n-1) - Q(n-2) - ... - Q(m)
     = U(n) - (U(n-1)-Q(n-1))*a   - Q(n-1) - Q(n-2) - ... - Q(m)
     = U(n) - U(n-1)*a + Q(n-1)*a - Q(n-1) - Q(n-2) - ... - Q(m)
     =                   Q(n-1)*a - Q(n-1) - Q(n-2) - ... - Q(m)


Хотя где-то я, всё-таки, налажал... Результаты по формуле — меньше результатов брутфорса.
Перекуём баги на фичи!
Re[3]: Разборчивые строки
От: watchmaker  
Дата: 10.07.18 13:40
Оценка:
Здравствуйте, Кодт, Вы писали:



К>Поставил натурный эксперимент на a=3, n=15, k=5. (Тупо проверил все 14 миллионов строк). Получил R=14364157, Q=738

К>Хотя где-то я, всё-таки, налажал...
В общем-то, тот факт, что R получилось больше чем 315 (то есть больше чем число всевозможных слов) уже выглядит подозрительно. Не говоря уж о том, что Q+R будет ещё больше

К>>Попробуем построить строки длины n с повторами.

К>R(n) = R(n-1) * a     -- дописали произвольную букву к каждой строке с повторами
К>     ...
К>     + Q(n-2)         -- взяли строку без повторов и удвоили ей хвост длины 2

К>но КАЖЕТСЯ, что мы действительно имеем дело с непересекающимися классами, поэтому можем суммировать как есть.
Круто, но процитированные классы таки пересекаются.
Например, строку "ABABA" можно получить из "ABAB" добавлением "A", либо из "ABA" добавлением суффикса "BA".
То есть в этой уникальной строке повторяются разные подстроки: "ABABA" и "ABABA", и она оказывается посчитанной дважды.
Re[4]: Разборчивые строки
От: Кодт Россия  
Дата: 11.07.18 10:18
Оценка:
Здравствуйте, watchmaker, Вы писали:

К>>но КАЖЕТСЯ, что мы действительно имеем дело с непересекающимися классами, поэтому можем суммировать как есть.

W>Круто, но процитированные классы таки пересекаются.
W>Например, строку "ABABA" можно получить из "ABAB" добавлением "A", либо из "ABA" добавлением суффикса "BA".
W>То есть в этой уникальной строке повторяются разные подстроки: "ABABA" и "ABABA", и она оказывается посчитанной дважды.

Да, именно это у меня и вышло в брутфорсе.
Получается, что надо дробить класс Q(n) строк без повторов длины n на подклассы вида Q(n, t1,t2,...) где ti — длина паразитного повтора
Подкласс t=2
xxxABA + BA = xxxABABA
                 --==
Подкласс t=3
xxxABCA + BCA = xxxABCABCA
                   ---===
Подкласс t=4
xxxABCDA + BCDA = xxxABCDABCDA
                     ----====

Подкласс t=2,4         --==
xxxABACA + CA   = xxxABACACA
xxxABACA + BACA = xxxABACABACA
                     ----====

Подкласс t=2,5
xxxABCADA + DA
xxxABCADA + BCADA

Подкласс t=3,5
xxxABACDA + CDA
xxxABACDA + BACDA


Тогда формула будет выглядеть вот так:
Q(n) = Q(n-1)*a
     - Q(n-1)             - повторы длины 1
     - Q(n-2) + Q(n-2,2)  - поскольку мы
     - Q(n-3) + Q(n-3,2) + Q(n-3,3)
     - Q(n-4) + Q(n-4,2) + Q(n-4,3) + Q(n-4,4) - Q(n-4,2,4)
     . . .

(если ничего не путаю)

Длины паразитных повторов — это положения вхождений последней буквы, с конца.
И вот это мне очень не нравится: чтобы за ними проследить, нам придётся не расписывать формулу для каждого подкласса
Q(n,t1) = Q(n-1,t1)*a + ... — ...,
а в завуалированной форме рожать все строки. А их, понятное дело, экспонента по основанию мощности алфавита (a). (Ну, ближе к (a-1), но всё равно не сахар).
Перекуём баги на фичи!
Re[5]: Разборчивые строки
От: Serge  
Дата: 11.07.18 14:57
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, watchmaker, Вы писали:


К>>>но КАЖЕТСЯ, что мы действительно имеем дело с непересекающимися классами, поэтому можем суммировать как есть.

W>>Круто, но процитированные классы таки пересекаются.
W>>Например, строку "ABABA" можно получить из "ABAB" добавлением "A", либо из "ABA" добавлением суффикса "BA".
W>>То есть в этой уникальной строке повторяются разные подстроки: "ABABA" и "ABABA", и она оказывается посчитанной дважды.

К>Да, именно это у меня и вышло в брутфорсе.

К>Получается, что надо дробить класс Q(n) строк без повторов длины n на подклассы вида Q(n, t1,t2,...) где ti — длина паразитного повтора
К>
К>Подкласс t=2
К>xxxABA + BA = xxxABABA
К>                 --==
К>Подкласс t=3
К>xxxABCA + BCA = xxxABCABCA
К>                   ---===
К>Подкласс t=4
К>xxxABCDA + BCDA = xxxABCDABCDA
К>                     ----====

К>Подкласс t=2,4         --==
К>xxxABACA + CA   = xxxABACACA
К>xxxABACA + BACA = xxxABACABACA
К>                     ----====

К>Подкласс t=2,5
К>xxxABCADA + DA
К>xxxABCADA + BCADA

К>Подкласс t=3,5
К>xxxABACDA + CDA
К>xxxABACDA + BACDA
К>


К>Тогда формула будет выглядеть вот так:

К>
К>Q(n) = Q(n-1)*a
К>     - Q(n-1)             - повторы длины 1
К>     - Q(n-2) + Q(n-2,2)  - поскольку мы
К>     - Q(n-3) + Q(n-3,2) + Q(n-3,3)
К>     - Q(n-4) + Q(n-4,2) + Q(n-4,3) + Q(n-4,4) - Q(n-4,2,4)
К>     . . .
К>

К>(если ничего не путаю)

К>Длины паразитных повторов — это положения вхождений последней буквы, с конца.

К>И вот это мне очень не нравится: чтобы за ними проследить, нам придётся не расписывать формулу для каждого подкласса
К>Q(n,t1) = Q(n-1,t1)*a + ... — ...,
К>а в завуалированной форме рожать все строки. А их, понятное дело, экспонента по основанию мощности алфавита (a). (Ну, ближе к (a-1), но всё равно не сахар).

Что-то похожее на принцип включения-исключения. Но как его применить до конца пока не приходит в голову.
Re[4]: Разборчивые строки
От: Кодт Россия  
Дата: 11.07.18 15:39
Оценка:
Здравствуйте, watchmaker, Вы писали:

К>>Поставил натурный эксперимент на a=3, n=15, k=5. (Тупо проверил все 14 миллионов строк). Получил R=14364157, Q=738

К>>Хотя где-то я, всё-таки, налажал...
W>В общем-то, тот факт, что R получилось больше чем 315 (то есть больше чем число всевозможных слов) уже выглядит подозрительно. Не говоря уж о том, что Q+R будет ещё больше

Ещё и в подсчётах налажал
Q=738, R=14.348.169

Для a=4, n=15, k=5 получается Q=4.182.288, R=1.069.559.536, U=1.073.741.824
Перекуём баги на фичи!
Re: Разборчивые строки
От: Sergei MO Россия  
Дата: 11.07.18 16:43
Оценка:
Здравствуйте, Serge, Вы писали:

S>Есть такая задача: требуется найти количество строк длины n (n <= 100), которые не содержат в себе тандемных повторов длины от 1 до k (1 <= k <= 9) над заданным алфавитом из а букв (1 <= a <= 26). Тандемным повтором называется строка вида ww, где w — это некоторая последовательность букв (в этой последовательности от 1 до k букв).


Какие ограничения на время выполнения?
Требуется точный ответ, или достаточно остатка по какому-нибудь модулю?

Для максимальной размерности (100, 9, 26) ответ будет примерно таким:

55398695630082263777990545259715539382135941833969449880383466446514719383338980765333985063221872678340631054012466380215694892163495976800

Re[2]: Разборчивые строки
От: Кодт Россия  
Дата: 12.07.18 12:05
Оценка:
Здравствуйте, Sergei MO, Вы писали:

SM>Для максимальной размерности (100, 9, 26) ответ будет примерно таким:


"примерно"

SM>55398695630082263777990545259715539382135941833969449880383466446514719383338980765333985063221872678340631054012466380215694892163495976800


Каким способом считал? Сколько времени заняло?
Перекуём баги на фичи!
Re: Разборчивые строки
От: Кодт Россия  
Дата: 12.07.18 12:34
Оценка:
Здравствуйте, Serge, Вы писали:

S>Будут идеи?


Есть вот какая идея.

Допустим, мы не поленились и нашли множество строк без повторов (далее — просто "строк") длиной k, всего Q1 штук.
Каждой строке сопоставим компоненту вектора количества V_k, там будет, разумеется, 1. (По одной уникальной строке во множестве).

После этого не поленились второй раз, склеили все со всеми и отфильтровали.
Эту операцию закодируем как матрицу M размером Q1*Q1, с единичками, где склеивание было успешным (получилась строка без повторов) и ноликами, где неуспешным.
Если теперь сгруппируем полученные строки длины 2k по равенству хвостов длиной k, — то количество строк в этих классах — это вектор V_2k,
V_2k = M*V_k.
Общее количество строк в множестве — очевидно, сумма компонентов вектора V_2k.

Этот фокус можно повторять до тех пор, пока длина t*k <= n.
V_tk = (M^t) * V_k.

После чего построим матрицу M' склеивания V_k с V_(n%k) — тут меня настигла лень, чтоб расписать технические подробности, как что группировать и где там какие значения компонентов будут.
Но в итоге V_n = M' * M^(n/k-1) * V_k.

Так мы можем сэкономить на возведении матрицы в степень, ну и обязательно экономим на размерности — вместо перечисления всех строк и проверке всех длин оперируем только строками длины k, да и то лишь три раза:
— когда порождаем множество
— когда порождаем одну и вторую матрицы

Следующий момент для сокращения размерности: если 2k < a, то мы можем взять алфавит мощности 2k, и сказать "остальные строки — с точностью до подстановки!"
При этом вектор V_k уменьшается в размерах, но вместо единичек в нём будут количества подстановок соответствующих строк: (a-s)! где s — количество уникальных букв в строке.
Поскольку M — это "все со всеми", там будут не единички, а тоже количества подстановок...
Но вот здесь уже мысль совсем иссякла, как корректно учитывать, что там творится с алфавитом и с множителями.
Перекуём баги на фичи!
Re[2]: Разборчивые строки
От: Serge  
Дата: 12.07.18 12:44
Оценка:
Здравствуйте, Sergei MO, Вы писали:

SM>Здравствуйте, Serge, Вы писали:


S>>Есть такая задача: требуется найти количество строк длины n (n <= 100), которые не содержат в себе тандемных повторов длины от 1 до k (1 <= k <= 9) над заданным алфавитом из а букв (1 <= a <= 26). Тандемным повтором называется строка вида ww, где w — это некоторая последовательность букв (в этой последовательности от 1 до k букв).


SM>Какие ограничения на время выполнения?

SM>Требуется точный ответ, или достаточно остатка по какому-нибудь модулю?

SM>Для максимальной размерности (100, 9, 26) ответ будет примерно таким:

SM>

SM>55398695630082263777990545259715539382135941833969449880383466446514719383338980765333985063221872678340631054012466380215694892163495976800


Ответ можно по модулю, например 1e9+7.
Re[3]: Разборчивые строки
От: Sergei MO Россия  
Дата: 12.07.18 17:43
Оценка:
Здравствуйте, Кодт, Вы писали:

К>"примерно"

Всегда есть шанс, что я где-то накосячил

К>Сколько времени заняло?

С длинной арифметикой ~50 секунд, с остатком по модулю ~15 секунд.

К>Каким способом считал?


Рассмотрим следующее отображение исходных строк в алфавит 'A'..'Z':
сначала заменим последний символ строки и все его вхождения на 'A',
потом заменим последний незаменённый символ и все его вхождения на 'B',
и так далее, пока все символы строки не будут заменены. Например:
axxcae -> axxcaA -> BxxcBA -> BxxCBA -> BDDCBA

Будем называть такую строку шаблоном. У шаблонов есть следующие свойства:
1) каждая строка соответствует ровно одному шаблону;
2) если в шаблоне s различных символов, то ему соответствует a!/(a-s)! различных строк;
3) шаблон содержит все символы из диапазона 'A'..'A'+s-1 и только их;
4) если шаблон не содержит тандемных повторов длины k, то и соответствующие строки не содержат тандемных повторов длины k.


Далее рассмотрим множество шаблонов длины L, не содержащих тандемных повторов длины <= k.
Разобъём это множество на классы, класс будем описывать парой (суффикс шаблона, количество различных символов в шаблоне).
Длина суффикса выбирается так, чтобы это был наибольший суффикс, который может полностью войти в тандемный повтор.
Например для k = 4:
Максимальное количество символов в тандемном повторе длины 4 равно 8, поэтому суффиксы длины более 7 войти в тандемный повтор не могут.
Чтобы в повтор вошёл суффикс длины 7 для него должно выполняться условие prefix(3)==suffix(3):
ABCDABC + D = ABCD + ABCD

Если это условие не выполняется, то суффикс никак не сможет образовать тандемный повтор.
Аналогично для суффикса длины 6 должно выполняться условие prefix(2)==suffix(2):
ABСDAB + СD = ABCD + ABCD

Для суффикса длины 5 возможны уже два варианта — участие в повторе длины 4 и в повторе длины 3:
ABСDA + BСD = ABCD + ABCD
ABСAB + С = ABC + ABC

Соответственно, должно выполняться условие prefix(1)==suffix(1) или prefix(2)==suffix(2).
И так далее.


Всего таких классов получается около полумиллиона, поэтому дальше решаем обычным динамическим программированием: зная количества шаблонов в каждом классе для длины L, мы можем посчитать их количества для длины L+1. Ну и в конце учитываем, что каждому шаблону соответствуют много строк.
Re[4]: Разборчивые строки
От: Кодт Россия  
Дата: 14.07.18 20:37
Оценка:
Здравствуйте, Sergei MO, Вы писали:

SM>Рассмотрим следующее отображение исходных строк в алфавит 'A'..'Z':


Ну это понятно, "с точностью до подстановок/перестановок"

SM>Далее рассмотрим множество шаблонов длины L, не содержащих тандемных повторов длины <= k.

SM>Разобъём это множество на классы, класс будем описывать парой (суффикс шаблона, количество различных символов в шаблоне).
SM>Длина суффикса выбирается так, чтобы это был наибольший суффикс, который может полностью войти в тандемный повтор.

Вот это неясно: строка без повторов с суффиксом наибольшей длины, который может войти в повтор... но не входит же?

Имеется в виду, что строка вида P+S — без повторов, P+S+S — очевидно, с повтором, но P+S+S-последняя буква — тут возможны варианты, которые мы и начнём учитывать?
Или что-то другое?

SM>Чтобы в повтор вошёл суффикс длины 7 для него должно выполняться условие prefix(3)==suffix(3):

SM>
ABCDABC + D = ABCD + ABCD


Почему не учтены такие варианты
AB.CDE.AB + CDE = ABCDE.ABCDE
A.BCDEF.A + BCDEF = ABCDEF.ABCDEF

Как учитывается, что с одного суффикса можно сделать несколько разных повторов
A.BADAB.A + BADAB = ABADAB.ABADAB
ABA.D.ABA + D = ABAD.ABAD

SM>Если это условие не выполняется, то суффикс никак не сможет образовать тандемный повтор.


SM>Всего таких классов получается около полумиллиона, поэтому дальше решаем обычным динамическим программированием: зная количества шаблонов в каждом классе для длины L, мы можем посчитать их количества для длины L+1. Ну и в конце учитываем, что каждому шаблону соответствуют много строк.


Покажи, как совершается переход к L+1. Какие классы переходят в какие классы.
Как вводятся новые буквы (а пока L<A, они точно будут вводиться).
Перекуём баги на фичи!
Re[5]: Разборчивые строки
От: Sergei MO Россия  
Дата: 15.07.18 13:02
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вот это неясно: строка без повторов с суффиксом наибольшей длины, который может войти в повтор... но не входит же?

Прямо сейчас не входит, но если добавить один или более символов, то сможет войти.

К>Имеется в виду, что строка вида P+S — без повторов, P+S+S — очевидно, с повтором, но P+S+S-последняя буква — тут возможны варианты, которые мы и начнём учитывать?

Не совсем. P+S — без повторов, при этом существует строка ненулевой длины X, такая что P+S+X = P+W+W. Здесь важный момент, что правая граница P одна и та же и для суффикса и для повтора.
Если такой строки X не существует, то укорачиваем суффикс на один символ и повторяем проверку.

К>Почему не учтены такие варианты

К>AB.CDE.AB + CDE = ABCDE.ABCDE
К>A.BCDEF.A + BCDEF = ABCDEF.ABCDEF
Я рассматривал пример для k=4. Для бОльших k их нужно будет учесть.

К>Как учитывается, что с одного суффикса можно сделать несколько разных повторов

К>A.BADAB.A + BADAB = ABADAB.ABADAB
К>ABA.D.ABA + D = ABAD.ABAD
Мы выбираем длину суффикса так, чтобы все эти повторы были возможны. Больше никак не учитывается.

К>Покажи, как совершается переход к L+1. Какие классы переходят в какие классы.

К>Как вводятся новые буквы (а пока L<A, они точно будут вводиться).
Пусть есть класс (S,s), где S — суффикс шаблона, s — количество различных символов в шаблоне. Будем по очереди добавлять к суффиксу символы от 'A' до 'A'+s, для каждого символа делаем следующее:
1) если в новом суффиксе появился повтор, то переход по этому символу нам не интересен;
2) определяем необходимую длину нового суффикса;
3) проводим нормализацию суффикса с помощью операции отображения алфавитов;
4) пусть получился суффикс S1, тогда для символа 'A'+s переход будет в класс (S1,s+1), а для остальных символов — в класс (S1,s).

Например, при k=2:
(ABA, 4) + A  =>  ABAA  =>  повтор A+A
(ABA, 4) + B  =>  ABAB  =>  повтор AB+AB
(ABA, 4) + C  =>  ABAC  =>  AC  =>  BA  => (BA, 4)
(ABA, 4) + D  =>  ABAD  =>  AD  =>  BA  => (BA, 4)
(ABA, 4) + E  =>  ABAE  =>  AE  =>  BA  => (BA, 5)

Первые два перехода приведут к повторам, остальные переходы после корректировки длины суффикса и нормализации приведут к одному и тому же суффиксу, но разным количествам символов.
Для символов 'F'..'Z' после нормализации получается такой же шаблон, как для символа 'E', поэтому их не добавляем, чтобы не посчитать этот шаблон несколько раз.


PS. В прошлом посте я забыл нормализовать суффиксы в примерах. Это может вводить в некоторое заблуждение.
Re[4]: Разборчивые строки
От: Serge  
Дата: 30.07.18 16:40
Оценка:
Здравствуйте, Sergei MO, Вы писали:

SM>Всего таких классов получается около полумиллиона, поэтому дальше решаем обычным динамическим программированием: зная количества шаблонов в каждом классе для длины L, мы можем посчитать их количества для длины L+1. Ну и в конце учитываем, что каждому шаблону соответствуют много строк.


Около полумиллиона для k=9,n=100,a=26? Хм, у меня для k=9,n=18,a=26 число состояний 726K, ответ по модулю 10^9+7 -- 113932797. А какой язык использовался для тестов?
Re: Решение
От: Sergei MO Россия  
Дата: 01.08.18 20:38
Оценка:
Здравствуйте, Serge, Вы писали:

S>Есть такая задача: требуется найти количество строк длины n (n <= 100), которые не содержат в себе тандемных повторов длины от 1 до k (1 <= k <= 9) над заданным алфавитом из а букв (1 <= a <= 26). Тандемным повтором называется строка вида ww, где w — это некоторая последовательность букв (в этой последовательности от 1 до k букв).


  Код решения
#include <vector>
#include <map>
#include <string>
#include <iostream>

using namespace std;

class Solver
{
    // группа состояний с одинаковыми суффиксами
    struct Group
    {
        // (символ, индекс группы)
        vector<pair<int, int>> transitions;
        vector<int> states;
    };

    // параметры задачи
    int K, A, Mod;
    // количество состояний
    int states;
    // группы состояний
    vector<Group> groups;
    // отопражение суффикс -> индекс группы
    map<string, int> mapping;
    // переходы между состояниями 
    vector<pair<int, int>> transitions;

private:
    // возвращает true, если подстроки (start1, length) и (start2, length) равны
    bool areEqual(const string& s, int start1, int start2, int length)
    {
        for (int i = 0; i < length; i++)
        {
            if (s[start1 + i] != s[start2 + i])
            {
                return false;
            }
        }
        return true;
    }

    // возвращает true, если строка заканчивается тандемным повтором длины <= k
    bool endsWithRepeat(const string& s, int k)
    {
        while (k > 0)
        {
            if (s.size() >= 2 * k)
            {
                if (areEqual(s, s.size() - k, s.size() - 2 * k, k))
                {
                    return true;
                }
            }
            k -= 1;
        }
        return false;
    }

    // возвращает кратчайший суффикс, который не теряет информацию о будущих тандемных повторах длины <= k
    string getShortestSuffix(const string& s, int k)
    {
        size_t start = 0;
        while (start < s.length())
        {
            for (int len = k; len > 0; len--)
            {
                int exc = start + 2 * len - (int)s.length();
                if (exc <= 0)
                {
                    break;
                }
                if (areEqual(s, start, start + len, len - exc))
                {
                    return s.substr(start);
                }
            }
            start += 1;
        }
        throw 0;
    }

    // приводит суффикс к нормализованнному виду
    // только для суффиксов вида "нормализованный суффикс + один произвольный символ"
    void normalize(string& s)
    {
        for (size_t i = 0; i < s.length() - 1; i++)
        {
            if (s[i] == s.back())
            {
                s[i] = 'A';
            }
            else if (s[i] < s.back())
            {
                s[i] += 1;
            }
        }
        s.back() = 'A';
    }

    // создаёт группы состояний, достижимых из группы с указанным суффиксом
    int createGroups(const string& suffix)
    {
        if (mapping.find(suffix) == mapping.end())
        {
            int group = groups.size();
            groups.push_back(Group());
            groups.back().states.resize(A, -1);
            mapping[suffix] = group;
            for (int i = 0; i < A; i++)
            {
                string temp = suffix + string(1, 'A' + i);
                if (!endsWithRepeat(temp, K))
                {
                    temp = getShortestSuffix(temp, K);
                    normalize(temp);
                    groups[group].transitions.push_back(make_pair(i, createGroups(temp)));
                }
            }
        }
        return mapping[suffix];
    }

    // создаёт массив переходов между состояниями
    int createStates(int group, int chars)
    {
        int state = groups[group].states[chars - 1];
        if (state < 0)
        {
            state = states++;
            groups[group].states[chars - 1] = state;
            for (auto it = groups[group].transitions.begin(); it != groups[group].transitions.end(); ++it)
            {
                if (it->first < chars)
                {
                    transitions.push_back(make_pair(state, createStates(it->second, chars)));
                }
                else if (it->first == chars)
                {
                    transitions.push_back(make_pair(state, createStates(it->second, chars + 1)));
                }
                else
                {
                    break;
                }
            }
        }
        return state;
    }

    void add(int& res, int arg)
    {
        res += arg;
        if (res > Mod)
        {
            res -= Mod;
        }
    }

    void mul(int& res, int arg)
    {
        res = (long long)res * arg % Mod;
    }

public:
    Solver(int k, int a, int mod) : K(k), A(a), Mod(mod), states(0)
    {
        createStates(createGroups("A"), 1);
    }

    int solve(int n)
    {
        vector<int> curr(states, 0);
        vector<int> next(states, 0);
        curr[0] = 1;
        for (int i = 2; i <= n; i++)
        {
            for (int j = 0; j < transitions.size(); j++)
            {
                add(next[transitions[j].second], curr[transitions[j].first]);
            }
            curr.assign(next.begin(), next.end());
            next.assign(states, 0);
        }

        int res = 0;
        for (int i = 0; i < groups.size(); i++)
        {
            for (int j = 0; j < groups[i].states.size(); j++)
            {
                if (groups[i].states[j] >= 0)
                {
                    int cnt = curr[groups[i].states[j]];
                    for (int k = 0; k <= j; k++)
                    {
                        mul(cnt, A - k);
                    }
                    add(res, cnt);
                }
            }
        }
        return res;
    }
};

#define NOMINMAX
#include <Windows.h>

int main()
{
    LARGE_INTEGER start;
    QueryPerformanceCounter(&start);

    int k = 9;
    int a = 26;
    int n = 100;
    int mod = 1000000007;

    Solver s(k, a, mod);
    int res = s.solve(n);

    LARGE_INTEGER end, freq;
    QueryPerformanceCounter(&end);
    QueryPerformanceFrequency(&freq);

    cout << "k = " << k << endl;
    cout << "a = " << a << endl;
    cout << "n = " << n << endl;
    cout << "mod = " << mod << endl;
    cout << "res = " << res << endl;
    cout << "time = " << (end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart << " ms" << endl;
    return 0;
}


Результат:

k = 9
a = 26
n = 100
mod = 1000000007
res = 157188119
time = 3986 ms

Re[5]: Разборчивые строки
От: Sergei MO Россия  
Дата: 01.08.18 20:42
Оценка:
Здравствуйте, Serge, Вы писали:

S>Хм, у меня для k=9,n=18,a=26 число состояний 726K, ответ по модулю 10^9+7 -- 113932797.

Ответ верный. Состояний для такой длины достаточно ~155К.

S>А какой язык использовался для тестов?

Я выложил решение ответом первого уровня.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.