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

Автор Krot, 14 лет назад, По-русски
Никак немогу понять всю пользу использования хэширования в задачах на строки. Складывается впечатление, что это нужно только для быстрого сравнения строк. Тем не менее, слышал, что многие задачи как-то очень просто решаются с использованием хэширования. Например:

http://acm.timus.ru/problem.aspx?space=1&num=1517 (умею решать за линейное время суффиксным деревом, слышал, что решается просто за O(nlogn) хэшами)

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

UPD. Спасибо, всем, теперь появился немного другой вопрос: можно ли использовать hash_map на соревнованиях (точнее есть ли какие-то соревнования, где таковых библиотек нету)? А то вроде как они не стандартизованы?

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

14 лет назад, # |
Rev. 4   Проголосовать: нравится +1 Проголосовать: не нравится
Знаешь КМП (алгоритм Кнута-Морриса-Пратта), нахождения подстроки в строке? То же самое можно сделать через хэширование (алгоритм Рабина-Карпа).
14 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится
Timus 1517 решается бинпоиском по длинне + хэшмапой и алгоритмом Рабина-Карпа
14 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится
Ну по крайней мере в первой задаче за O(n log2n) можно совсем просто: бинпоиском фиксируем длину ответа и за O(n log n) хэшами проверяем, если две совпадающие строки.

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

Но и здесь, и там хэши используются именно что для быстрого сравнения строк.
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    По поводу 2 пункта:
    Пробегаемся по всей, например первой строке, вычисляя хэш функцию на подстроке и за O( log(sqr(n)) ) ищем это значение среди O(n*n) хэшей второй строки. Я правильно понял?
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Только вот наверное надо будет сортировать второй список хэшей, но их там O(n*n). В обшем, можно поподробнее 2 пункт?
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Речь идёт о первой задаче? При фиксированной длине строки у нас O(n) хэшей, каждый из которых можно вычислить за O(1), если сделать очевидную предобработку.  
      • 14 лет назад, # ^ |
        Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится
        Да, речь идет о первой задаче
      • 14 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Непонятно как "за O(n log n) хэшами проверяем, если две совпадающие строки"
      • 14 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Если мы вычислили хэш, как его найти во второй строке?
        • 14 лет назад, # ^ |
            Проголосовать: нравится +1 Проголосовать: не нравится
          А, все понял: просто сольем две строки и все
          • 14 лет назад, # ^ |
              Проголосовать: нравится +1 Проголосовать: не нравится
            Складываем эти две строчки, потом бинпоиском по длине считаем хэши для строк нужной нам длины, сортируем этот список хэшей и проверяем, нет ли двух одинаковых?
            • 14 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              нет, извиняюсь неверно)
            • 14 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Ну, это не правильно: ты можешь найти две одинаковые подстроки в одной и той же строке.
              • 14 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                Да, я поторопился, поэтому и написал "неверно".
        • 14 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится
          Смотри, фиксируем длину строки k. Выписываем O(n) хэшей подстрок длины k в первой строке, и столько же во второй. Теперь у нас есть два набора из n чисел, надо надо найти пару одинаковых. Это можно сделать ровно 100500 способами за O(n log n), например отсортировав оба набора и пробежавшись по ним двумя указателями.
          • 14 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            Да, да, я все понял, огромное спасибо!). Не подскажите каких-нибудь интересных задач или ключевых идей на эту тему?
            • 14 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Могу посоветовать
              .
              Решается хешами и совсем не просто как по мне  - я долго мучался на грани ТЛ/МЛ/ВА:)
              • 14 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                ключевым шагом для АС у меня было замена типа  хеша с long long на int =)
              • 14 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                Это пример задачи, которую хешами лучше не решать. Ее лучше бором.
              • 14 лет назад, # ^ |
                Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
                Не могли бы Вы посоветовать что-нибудь, чтобы решить эту задачу? Я очень долго мучился, так и не смог пройти ТЛ 3 тест. 

                Мое решение: для каждой строки делаю бинпоиск по длине К. Для каждой длины К - запускаю функцию, где заношу все хеши длины К других строк в таблицу. После перебираю все подстроки текущей строки длины К.
                Естественно, таблицу, по выходу из этой функции, очищаю.
                Надеюсь, я понятно объяснил. 64битный тип на 32 уже поменял.


                Thx in advance
                • 14 лет назад, # ^ |
                    Проголосовать: нравится 0 Проголосовать: не нравится
                  Скорее ошибка в реализации, т.к. первый большой тест - это девятый. Может быть вы ошиблись при выполнении бинпосика.
                  • 14 лет назад, # ^ |
                      Проголосовать: нравится 0 Проголосовать: не нравится
                    Спасибо за ответ. Но, по-моему, зря Вы сомневаетесь  в моем бинпоиске.
                    Основная идея является правильной?
                    • 14 лет назад, # ^ |
                      Rev. 2   Проголосовать: нравится +1 Проголосовать: не нравится

                      [увеличваем_строку_чтобы_текст_нормально_читался_а_не_по_слову_в_строке]

                      бинпоиск не нужен. делаем цикл по длине от 1 до 100
                      X кидаем все подстроки длины k всех строк в хеш таблицу
                      X перебираем строки для которых еще нет ответа
                      XX выкидываем из хеш таблицы все подстроки длины k нашей фиксированной строки
                      XXX смотрим в хеше есть то что нас интересует или нет
                      XX добавляем в хеш таблицу все подстроки длины k нашей фиксированной строки

                      • 14 лет назад, # ^ |
                          Проголосовать: нравится 0 Проголосовать: не нравится
                        Спасибо_за_развернутый_и_полный_ответ. Буду_пробовать. Оказывается, моя идея была в корне не правильной.
                        • 14 лет назад, # ^ |
                            Проголосовать: нравится 0 Проголосовать: не нравится

                          _______________Не_хочу_узкий_текст_____________

                          А я решил бинпоиском по длине. Далее перебираем все хеши строк длины k. Сортируем и находим есть ли совпадающие хеши.

                          Асимптотика nlog^2n (logn бинпоиск, nlogn - проверка).

                          Так что такое решение допустимо :)

            • 14 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              http://acm.timus.ru/problem.aspx?space=1&num=1414
              http://acm.timus.ru/problem.aspx?space=1&num=1542
              Хэш+Сет я делал.

              Попробуй еще просто набор строк отсортировать по алфавиту за N*LogN*LogM, N - кол-во строк, M - длина строк. Бинарный поиск по совпадающему хэшу.

              Еще хэшированием найти мининимальный циклический сдвиг. Тоже бинарный поиск по совпадающему хэшу.

              Да и вообще много задач разных.
              • 14 лет назад, # ^ |
                Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
                Как ты 1542 задачу делал ? Можешь плз по подробнее рассказать алгоритм с хэшами?
          • 13 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            Пусть у Вас есть набор хешей длины k первой строки и второй строки. Вы же не можете просто в лоб сравнить их, т.к. эти хеши умножены на разные коофициенты (т.е. hash_of_string1 = s1[i]*p^i + s1[i+1*p^(i+1) + s1[i+k-1]*p^(i+k-1)
            hash_of_string2 = s2[j]*p^j + s2[j+1]*p^(j+1) + s2[j+k-1]*p^(j+k-1)
            Т.к. i != j и строки взяты по какому-то модулю, просто их сравнить нельзя.) Я в таких случаях домнажаю первый хеш на p^o1, а второй - на p^o2 так чтобы i + o1 = j + o2. Существует ли иное решение этой проблемы?  

          • 13 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            а за O(n) нельзя? :)
          • 12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            Получаем порядка n хэшей для каждой длины подстроки,поэтому сложность, по-моему, будет n*(n log n).

  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Вот помнится решал эту задачу хэшами + бин. поиск. Да только они так и не прошли, сколько не запихивал. Видимо, где-то коллизии были. Использовал хэширование, которое дано в e-maxx.
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    >Во второй, судя по всему, работает та же самая идея. Только хэш, наверное, нужно брать полиномиальный от двух переменных.


    а как брать хэш от произвольного квадрата в матрице?
    например (семпл из задачи 1448):
    5 10
    ljkfghdfas
    isdfjksiye
    pgljkijlgp
    eyisdafdsi
    lnpglkfkjl

    Допустим нужно вязть хэш от квадрата (0,0) (3,3)
    сначала берем хэши от строк (от строк понятно как брать), а потом полученные хэши как-то сложить? Если да, то как? 
    • 14 лет назад, # ^ |
        Проголосовать: нравится +1 Проголосовать: не нравится
      пусть у нас есть 2 простых числа - p и q
      тогда букве в прямоугольнике a[i,j] сопоставим величину a[i,j]*p^i*q^j (i, j, конечно же, считаются от левой и верхней границ выбранного прямоугольника). хэшом будет сумма этих величин по всему прямоугольнику.
      как после предпросчета всей таблицы за О(1) находить хэш для любого прямоугольника - догадаться несложно...
      • 14 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Спасибо понял, оказывается вообще не в ту сторону думал!) 
  • 13 лет назад, # ^ |
    Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится

    Сорри, может я тупой!Но непонятно по задаче 1517.Что значит фиксируем длину ответа?

    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Ты перебираешь бин. поиском длину ответа (длину строки, которая будет являться ответом на задачу). 
      int l = 0, r = s.length();
      while (r > l)
      {
      int m = (l + r)/2;
      if (f (m)) l = m; else r = m-1;
      }
      if (f(l)) cout << l; else cout << "-1";
      f(len) - это функция, которая проверяет может ли строка длины len быть ответом.
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Раз уж пошла речь о хэштровании: какие наиболее хорошии фукнции существуют, кроме той, которая описана на сайте e-maxx?
14 лет назад, # |
  Проголосовать: нравится -7 Проголосовать: не нравится
zaminusuyte menya plz!
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Тем, кто из вредности плюсует такие комменты, я бы посоветовал сводить рейтинг автора к 0. Это куда обиднее :)
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Например, вот эта задача очень просто решается хэшами: http://codeforces.net/problemset/problem/7/D
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Однажды мне один крутой асмер сказал, что он не знает ничего про строки кроме хешей и он сдает 90 % задач ими. Потом Саша Прудаев из Тюмени так вообще на всех контестах сдавал задачи на строки в первый час, которые другие сдавали в конце контеста и тоже хешами) Я пишу хеш почти всегда, когда вижу задачу на строки - потому что а) их написать быстро б) не так сложно придуать в) если не пройдет (вдруг), то будет прога которая более менее затестит нормальное решение.
примеры задач некоторые
1) http://acm.timus.ru/problem.aspx?space=1&num=1486
2) http://acm.timus.ru/problem.aspx?space=1&num=1590 - вообще подходит для любого алгоритма
3) http://acm.timus.ru/problem.aspx?space=1&num=1425 - не на строки, но я помню, сдавал хешами
Тут вообще много задач кинули, но если нужно еще задач интересных, пиши в личку)
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Первую за O(nlogn) решать так:
Бинпоиск по длине ответа. Пусть длина k. Тогда вычисляем O(n) полиномиальных хэшей подстрок длины k первой строки за время O(n) и складываем их в хэш-таблицу. Потом проходимся по подстрокам длины k второй строки и смотрим - лежить ли в хэш-таблице нужный хэш. После проверки для k хэш-таблицу аккуратно очищаем.

Вторую задачу абсолютно аналогичным образом можно решить за O(N^2logN).
14 лет назад, # |
  Проголосовать: нравится +9 Проголосовать: не нравится

Вот может будет полезно, я как раз недавно читал лекцию по полиномиальным хешам:

http://skidanovalex.ru/slides/phashes.pptx

 

  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Спасибо, интересно, только непонятно: там что-то недописано или это у меня у половины страниц только заголовки?
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Еще вопрос очень важный: какие именно хэши использовать? Я про одномернуй задачу(1517), и про двумерную(1486)
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Я и там и там писал полиномиальный
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      А какие ещё бывают О_о? Вернее, для каких целей может пригодиться какой-либо неполиномиальный хэш?
      • 14 лет назад, # ^ |
          Проголосовать: нравится +3 Проголосовать: не нравится
        Разные бывают методы хеширования) Но для строк я по-моему ни разу не писал не полиномиальный хеш, однако видел код, где был другой метод.
        Я просто не понял вопрос "какие именно хэши использовать?" - единственное, что пришло на ум - полиномиальные)
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Ответ на новый вопрос: нет, hash_map есть не везде. Например, везде где в качестве компилятора стоит студия, его нет.

Встречный вопрос: зачем тебе hash_map? :о) Если мне не изменяет память (поправьте меня, если я не прав), там медленная реализация, и если обычный map не проходит, hash_map не панацея -- все равно надо писать хеш табличку ручками.

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

    Разве нет в студии?

    #include <hash_set>
    #include <hash_map>

    using namespace std;
    using namespace stdext;

    int main () {
    hash_set <int> w;
     hash_map <int, int> w2;

    return 0;

    }

    Всё замечательно компилируется.

    upd: про hash_map не знаю, но hash_set работает медленее примерно в два раза в сравнении с ручной реализацией (как то давно тестировал немного).

    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Ну, кажется она ругается warningами на него. Когда нибудь они его выкинут (когда допилят аналогичную фичу в C++0x)
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Я просто увидел несколько решений с использованием hash_map'a, поэтому и спросил не рискуют ли люди на соревновании, когда так делают)
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
HashSet и HashMap используются часто. Но это Java...
14 лет назад, # |
Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится
Написал решение по второй задачи за O(n ^ 2 * log ^ 2 (n))
Но оно таймлимитится(((
Вот решение  http://pastebin.com/rHtX5KYe
подскажите плз что не так.
  • 14 лет назад, # ^ |
    Rev. 4   Проголосовать: нравится 0 Проголосовать: не нравится
    что еще тут можно с оптимизировать?

    Если использую  map то решение ловит мемори лимит, set не получается использовать проблема

     потом возникает в поиске.

    Кстати как использовать set так чтобы можно было найти элемент по одному только хэшу

    то есть

    set<pair<long long,pair<int,int> > > set;

    first это хэш и second это координаты области.

    вот как мне проверить есть ли  в set хэш.

    set.find(hash); - так не катит требует структуру pair<long long,pair<int,int> >
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      1) Могут сильно тормозить new / delete

      2) Думаю, что асимптотика O(n^2 * log^2 (n)) недостаточно хороша

      Мое решение O(n^2 * log(n)) с самописной hash_map без динамической памяти заходит за 0.3 с, а лишний log(n) раз в 8 замедляет решение -> TL

      • 14 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
        хм я даже не знаю где log(n) может быть лишний, 
        1-ый тот что по длине стороны квадрата идет.
        2-ой тот что проверяет наличие хэша,.

        попробую как нибудь избавить от new и delete
        • 14 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          log(n) от бинпоиска

          Вставка/поиск в дереве - log(n)

          Вставка/поиск в hash_map - O(1)

          Кстати, дерево у тебя без балансировки. 99%, конечно, что при хранении хешей его высота будет расти как O(log(n)), но все же

          • 14 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            >Вставка/поиск в hash_map - O(1), 

            круто, даже не знал, спасибо.
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Насчет того, как использовать set.
      можно завести структурку, типа:
      struct elem {
      long long hash;
      pair <int, int> c;
      elem (long long _hash): hash(_hash) {}
      };

      и перегрузить оператор <:
      bool operator < (const elem & a, const elem & b)
      {
      return a.hash < b.hash;
      }

      Тогда, по-идее, будет нормально работать set.find(elem(hash));
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

А какое лучше использовать простое число для вычисления хеша?

(Слышал что при использовании маленьких латинских букв хватит 31)

  • 14 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
    Ну, обычно стараются следовать 2 параметрам:
    1) модуль   p - просто число
    2) значение модуля   p близко размеру алфавита, желательно чуть больше. К примеру, для строчных английских букв отлично подходит что-то типа 29, 31.
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Я имел ввиду другое

      h(S) = S[0] *  P^N + S[1] * P^(N-1)+ S[2] * P^(N-2)+ S[3] * P^(N-3)+ ... + S[N]

      Я подразумевал простое число P, которое вы возводим в степень, а не модуль.

      • 14 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        это я плохо написал, я именно это просто число и имел в виду под модулем, сорри за непонятность.
        в качестве взятия по модулю мы обычно же ничего не делаем, беря хеш по 2  ^ (длина типа в битах)
14 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

14 лет назад, # |
Rev. 3   Проголосовать: нравится +3 Проголосовать: не нравится

Меня поражает до чего же глупы некоторые вопросы в этом топике.

 

Те, кто знает что такое хеш, ничего о нем не знает если не умеет написать хеш таблицу и не знает парадокса дней рождений. Кормена в руки и читать. Иногда проще написать хеш таблицу, чем бегать по громоздким итераторам мапы (недобно писать map<laja<blabla<int, LL>, int>, MyStruct>::iterator) и бояться использовать ссылки из-за того что память может быть перевыделена. Это такая же стандартная вешь как дейкстра или бфс, и ничем не сложнее.

 

Наблюдения, из своего опыта:

 

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

 

полиномиальный хеш от строки s это (s[0] * m^0 + ... + s[n - 1] * m^(n - 1) ) % P

 

P - модуль, m - множилка. Хорошая множилка, это такая множилка которая генерирует большую группу по модулю P. умножая m на себя, мы войдём в цикл, и чем больше будет длина этого цикла тем лучше. Хороший модуль это большой модуль, для которого существуют крутые множилки.

 

для простых чисел выбранных в качестве модуля, есть множилки, с длиной цикла P - 1 (то есть генераторы всех чисел от 1 до P - 1)

просто ли число m или нет не важно, важно группу какого размера оно генерирует.

 

в качестве P иногда берут число 2^32 или 2^64, используя обычные операции с 32-х и 64-х битовыми операциями и забивая на переполнения (в этом случае у нас все действия как раз по указанным модулям происходят).

В этом случае для того чтобы число m хоть что-то генерировало оно должно быть нечётным, а простое оно или нет это никого не волнует. Нечётность – необходимое условие, а простота числа m не является ни необходимым условием, ни достаточным, для того чтобы получить большую группу. Вообще порядки всех элементов являются делителями Phi(P), где Phi(P) – функция эйлера.

Следовательно для модуля 2^32 порядки элементов могут быть  только степенями двойки.

Чтобы вычислить порядок элемента, нужно возводить его в квадрат, пока оно не станет равно единице, 2^(количество шагов +-1 (лень думать))  и будет размером генерируемой группы.

Я юзаю для хешей некоторые числа которые помню и для которых знаю свойства, размеры групп, простоту или не простоту.

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

1517 заходит по одному хешу? писал по одному-wa 10 написал по двум — wa 32 юзал хеш таблицу я криворукий и можно написать по одному или нужно обязательно два писать?) но и в том случае я видимо накосячил.

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

    Рассматриваемых подстрок примерно 105 => при модуле около 1010 скорее всего будет коллизия. Таким образом, точно заходит 1 модуль порядка 1018 <=> 2 модуля порядка 109.