Блог пользователя GomerDoGo

Автор GomerDoGo, история, 3 недели назад, перевод, По-русски

Значит я спал себе спокойно и вдруг эта задача мне приснилась:

Дан массив a и массив b, надо разбить a на подотрезки и для каждого подотрезка поставить 1/0 — разворачиваем мы его или нет. Вопрос: надо определить можем ли мы из a получить b?

Я пока умею решать только за O(n^2), но явно же можно лучше, помогите плиз

  • Проголосовать: нравится
  • +63
  • Проголосовать: не нравится

»
3 недели назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Автокомментарий: текст был переведен пользователем GomerDoGo (оригинальная версия, переведенная версия, сравнить).

»
3 недели назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Автокомментарий: текст был обновлен пользователем GomerDoGo (предыдущая версия, новая версия, сравнить).

  • »
    »
    3 недели назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Что насчёт O(n log n)?

    • »
      »
      »
      3 недели назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится

      Что насчёт O(n log n)? Строим последовательность s длины 2n: s = a0,b0,a1,b1,... и ведём булево dp по границам между элементами a (граница i — это позиция 2i в s), где dp(i)=true означает, что префикс a длины i уже можно преобразовать в префикс b длины i. Любой блок разбиения a на отрезке [l, r) даёт переход dp(l) -> dp(r): если блок не переворачиваем, то требуется a[k]=b[k] для всех k из [l, r), и такие переходы делаются за O(n) протягиванием по подряд идущим равенствам a[k]==b[k] (держим самую дальнюю достижимую границу). Если блок переворачиваем, то условие b[l..r)=reverse(a[l..r)) эквивалентно тому, что подотрезок s на позиции [2l, 2*r) является палиндромом; поэтому переворотные переходы сводятся к палиндромам в s и добавляются через палиндромное дерево, а чтобы не перебирать все палиндромные суффиксы, используем стандартную оптимизацию diff/series_link и учитываем только палиндромы, начинающиеся и заканчивающиеся на чётных позициях (то есть попадающие в границы). Асимптотика: прочитать вход быстрее, чем за O(n), нельзя; часть без переворотов — O(n), палиндромное дерево строится за O(n), но DP через series_link в худшем случае даёт O(n log n) (так как на каждой позиции O(log n) серий), поэтому итог O(n log n) по времени и O(n) по памяти.

      • »
        »
        »
        »
        3 недели назад, скрыть # ^ |
         
        Проголосовать: нравится 0 Проголосовать: не нравится

        Мог бы ты объяснить почему нам хватает перебрать только log паллиндромов для позиции i? (если это очев и буквально объясняется series link, то можно сслыку плиз)

        • »
          »
          »
          »
          »
          3 недели назад, скрыть # ^ |
           
          Проголосовать: нравится 0 Проголосовать: не нравится

          Да, мне тут, или в личных сообщениях?

          • »
            »
            »
            »
            »
            »
            3 недели назад, скрыть # ^ |
             
            Проголосовать: нравится 0 Проголосовать: не нравится

            В eertree у каждого палиндрома v есть link(v) — следующий меньший суффикс‑палиндром. Вводят diff(v)=len(v)-len(link(v)). Если идти по link, то часто получаются длинные участки, где diff одинаковый; тогда длины палиндромов на этом участке отличаются на константу (это одна серия). series_link(v) (slink) сжимает такие участки: он переводит v в максимальный суффикс‑палиндром u, для которого diff(u) != diff(v), то есть к началу следующей серии. Поэтому в DP на фиксированной позиции достаточно обновиться по одному разу на серию, а не по каждому палиндрому.

            Ключевая лемма значится: длина цепочки v, slink(v), slink(slink(v)), .. (serial path) ограничена O(log n), поэтому серий на каждой позиции O(log n), и итоговая сложность DP получается O(n log n). См. введение diff/slink и утверждение про ограничение serial path (Approval 1) в разборе: https://mirror.codeforces.com/blog/entry/19193

»
3 недели назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

За количество делителей, умноженное на размер массива? Это меньше, чем квадрат

  • »
    »
    3 недели назад, скрыть # ^ |
    Rev. 2  
    Проголосовать: нравится 0 Проголосовать: не нравится

    Делителей не больше, чем корень

    bool check(int* a,int* b,int cnt,int len){
       bool ans=true;
       for(int block=0;block<cnt;++block){
          bool can2r=true,can2l=true;
          for(int i=0;i<len;++i){
             can2r&=a[block*len+i]==b[block*len+i];
             can2l&=a[block*len+i]==b[block*len+len-1-i];
          }
          ans&=can2r|can2l;
       }
       return ans;
    }
    bool check(int* a,int* b,int n){
       bool can=false;
       for(int c=1;c*c<=n;++c){
          if(n%c)continue;
          can|=check(a,b,c,n/c);
          can|=check(a,b,n/c,c);
       }
       return can;
    }
    
»
3 недели назад, скрыть # |
 
Проголосовать: нравится +11 Проголосовать: не нравится

hi , so i dreamt about the solution today in a nap ...

there was a fat horse who kept saying use 2 pointers+dp

»
3 недели назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

О, я не увидел, что отрезки не должны быть равными. А какое есть решение за квадрат?

  • »
    »
    3 недели назад, скрыть # ^ |
    Rev. 4  
    Проголосовать: нравится 0 Проголосовать: не нравится

    dp[i] = 0/1 можно ли выполнить эту задачу для префикса длины i.

    dp[i] |= dp[j-1] & (a[j..i] == b[j..i] | rev(a[j...i]) == b[j...i]) по всем j < i.

    Если на русском — говорим, что правая граница нашего отрезка сейчас в i, а левая в j, проверяем может ли так быть, перебирая все j<i. Вот и для проверки равенства строк будем использовать хеши, так что ассимптотика -> O(n^2)

»
3 недели назад, скрыть # |
 
Проголосовать: нравится +8 Проголосовать: не нравится

This is a very interesting problem!

I believe a solution can be found with DP[i] = whether solution exists up to position i, while only transitioning the shortest possible reversed interval and the shortest possible non-reversed interval from each position

If the next interval in any existing solution is not reversed and has length m, it can be partitioned into m intervals of length 1 each, so we will find it by repeatedly taking the shortest possible non-reversed interval

If the next interval in any existing solution is reversed, it gets more complicated. Suppose the shortest reversed interval we can take is [i, r1] and an existing solution has the next interval as [i, r2]. We can show that:

  • if [i, r1] does not cover the "center" of [i, r2] then a[i, r2] = S+T+S for some strings S and T and a[i, r1] = S, and we can still reach r2 by taking T next then S again
  • if [i, r1] does cover the "center" of [i, r2] then [i, r1] is not actually the shortest possible reversed interval (the symmetry of the center of [i, r2] is mirrored within [i, r1])

The shortest possible reversed interval from each position can be found in n*log(n) time by finding the largest possible interval from each possible center point with hashing + binary search, then applying sweepline

  • »
    »
    3 недели назад, скрыть # ^ |
     
    Проголосовать: нравится +10 Проголосовать: не нравится

    This is beautiful! Thank you a lot

    And if you are interested, in the Russian version of this post a guy proposed another solution:

    translation
»
3 недели назад, скрыть # |
 
Проголосовать: нравится -8 Проголосовать: не нравится

Probably related to this leetcode problem: https://leetcode.com/problems/scramble-string/description/.