Temat: unordered_map a zba9i.in

Ktoś doszedł czemu większość rozwiązań opartych na unordered_map wywalała się na teście 9i ('a'*150k+'b'*150k)?
W rankingu nie ma informacji o tym, kto na jakich testach się wywalił...

Obstawiam, że problemem może być słaba funkcja haszująca generująca wiele kolizji. Standardowe funkcje hashujące zaimplementowane w gcc std::hash są słabe (powiedziałbym nawet mocniej, że błędne).

"How to pick a hash function" https://sortingsearching.com/2020/05/21/hashing.html
> W rankingu nie ma informacji o tym, kto na jakich testach się wywalił...

No szkoda, ale są rozwiązania, można sobie sprawdzić. W szczególności wszystkie rozwiązania które dostały 9 punktów były oparte na unordered_map i wywalały się na teście 9i.
> Obstawiam, że problemem może być słaba funkcja haszująca generująca wiele kolizji.

Jest to możliwy powód, jednak w ramach dzielenia się wiedzą zdradzę, że nie o to chodziło w większości rozwiązań, które miały problemy na tym teście. Problemem jest niewłaściwe używanie unordered_mapy, a dokładniej metody clear(). Usuwa ona co prawda pary (klucz, wartość) z hashmapy, jednak nie zmniejsza zarezerwowanej pamięci, tzn. bucket_count() pozostaje bez zmian.

Dokładniej mówiąc, jeśli najpierw umieścimy w unordered_mapie dużo kluczy, a następnie dużo razy wyczyścimy ją w niewłaściwy sposób (tzn. jedynie używając metody clear()), to za każdym razem czas czyszczenia zależny będzie od rozmiaru zarezerwowanej pamięci, czyli liniowy, przez co całe rozwiązanie będzie kwadratowe.
> Problemem jest niewłaściwe używanie unordered_mapy, a dokładniej metody clear(). ...
O, dziękuję. Faktycznie o tym nie wiedziałem, a sprawdziłem, że w moim przypadku wystarczyło po wywołaniu clear() dodać rehash(2), żeby rozwiązać problem z zba9i. Chociaż równie dobrze i prościej można było zamienić unordered_map na map, chyba też by to przeszło.
Nie wiem czy warto zakładać nowy temat, więc może zapytam tu: jak wygląda rozwiązanie z unordered_map? To jest wzorcówka czy są inne podejścia?
"wzorcówka"? nie widziałem nigdzie żadnego oficjalnego rozwiązania, więc jak na razie chyba ciężko jakieś konkretne nazwać wzorcowym.

Ja robię tak, że robię DP[b-a][c-a] => liczba stringów kończących się na aktualnej pozycji, które mają liczby a,b,c takie, że te różnice są jakie są. Wtedy DP jest mapą z offsetem pomiędzy "logicznymi" a "fizycznymi" kluczami, żeby móc np. przesunąć wszystko o (-1, -1) jak wejdzie nowe a.
Ja inkrementowałem 7 map (7, bo było 7 możliwości podciągów zbalansowanych: zawierających tylko litery kolejno: a, b, c, ab, bc, ca i abc). Indeksem był balans pomiędzy literami zliczanymi przez daną mapę. Jednoliterowe mapy były 0-wymiarowe (1 element), dwuliterowe - jednowymiarowe (różnice ilości wystąpień tych dwóch liter), a trzyliterowa - dwuwymiarowa (2 z 3 indeksów map dwuliterowych). Jak wystąpiła litera nie zliczana przez daną mapę i na koniec, zliczałem dla każdego indeksu wartości v*(v-1)/2 z map i je czyściłem (gdybym miał unordered_map to też by mi się wywaliło na teście 9i). Dla abc konieczna była mapa, bo przestrzeń indeksów była O(n^2), co by się nie zmieściło w pamięci (natomiast różnych indeksów było max n).

Teraz widzę że zamiast czyścić mapy można było użyć nieużywanego wymiaru do zmiany koszyka i zliczać tylko na koniec.

Przy okazji: rozwiązania zniknęły :(
> Problemem jest niewłaściwe używanie unordered_mapy, a dokładniej metody clear().

Najgorsze, że standard języka C++ mówi, że m.clear() musi mieć złożoność O(m.size()).

https://en.cppreference.com/w/cpp/container/unordered_map/clear

Tylko jak się wczytać dokładniej w specyfikację, to okazuje się, że liczą tutaj tylko operacje na elementach, a nie całkowity czas działania metody.
W takim razie kolejne czyszczenia powinny być już szybkie, bo m.size() po pierwszym się zeruje. To wygląda na niezgodność ze standardem, zwłaszcza że jest wprost napisane, że nie chodzi o ilość koszyków tylko ilość elementów:

> DR LWG 2550
> Applied to C++11
> Behavior as published for unordered associative containers, unclear if complexity is linear in the number of elements or buckets
> Correct behavior clarified that it's linear in the number of elements
Okazuje się, że jednak jest technicznie zgodne ze standardem, bo:

https://eel.is/c++draft/container.requirements.general#2
> All of the complexity requirements in this Clause are stated solely in terms of the number of operations on the contained objects.

Jak mapa jest pusta, to clear() robi 0 operacji na elementach mapy -- ale ma prawo zrobić sobie ile chce innych obliczeń.
Rzeczywiście nie jest to powiedziane, a IMHO powinno, bo w ten sposób, to clear() mógłby robić sleepa na 5 sekund i też by było zgodne ze standardem :)
Łał, rzeczywiście paskudna pułapka, w życiu nie słyszałem o tym zachowaniu (i najwidoczniej ciężko je wyczytać z dokumentacji), a też się na to naciąłem.
Internally unordered map is implemented using Hash Table, the key provided to map are hashed into indices of a hash table that is why the performance of data structure depends on hash function a lot but on an average, the cost of search, insert and delete from the hash table is O.

http://www.indigocard.vip/