• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Szablon Bajtogrodu OI

Object Storage Arubacloud
0 głosów
266 wizyt
pytanie zadane 3 lutego 2023 w Algorytmy przez pasjonat_algorytmiki Pasjonat (19,540 p.)
edycja 3 lutego 2023 przez pasjonat_algorytmiki

Mam problem z takim zadaniem z 2 etapu OI-a: https://szkopul.edu.pl/problemset/problem/y-mTVYClxMJcgMhUnHaUqPPq/site/?key=statement

Zrobiłem kod na 50pkt w O(N^3) a mianowicie: ide DFS-em od każdego wierzchołka i dla każdego szablonu mam statystyki jakie krawędzie odwiedziliśmy i sprawdzam jaki się zgadza. Tylko kompletnie nie wiem co dalej, jak to przyśpieszyć.

Kod:

#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <set>

using namespace std;

int n = 0, a = 0, b = 0;
char wczytany_znak;
vector<vector<pair<int,char>>> krawedzie;
unordered_map<string,set<pair<int,int>>> stat;
vector<bool> czy_bylismy;
vector<string> wyn;

void DFS(int v, string napis, vector<int> wierzcholki)
{
    czy_bylismy[v] = true;
    wierzcholki.push_back(v);
    if (!napis.empty())
    {
        if (auto it = stat.find(napis) == stat.end())
            stat.insert({napis,{}});
        for (int i = 1; i < wierzcholki.size(); ++i)
        {
            stat[napis].insert({wierzcholki[i],wierzcholki[i-1]});
            stat[napis].insert({wierzcholki[i-1],wierzcholki[i]});
        }
    }
    for (int i = 0; i < krawedzie[v].size(); ++i)
    {
        if (czy_bylismy[krawedzie[v][i].first] == false)
        {
            string spr = napis;
            spr.push_back(krawedzie[v][i].second);
            DFS(krawedzie[v][i].first, spr, wierzcholki);
        }
    }
}

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    krawedzie.assign(n+1,{});
    czy_bylismy.assign(n+1,false);

    for (int i = 0; i < n-1; ++i)
    {
        cin >> a >> b >> wczytany_znak;
        krawedzie[a].push_back({b,wczytany_znak});
        krawedzie[b].push_back({a,wczytany_znak});
    }

    for (int i = 1; i <= n; ++i)
    {
        fill(czy_bylismy.begin(),czy_bylismy.end(),false);
        DFS(i,"",{});
    }

    for (auto it = stat.begin(); it != stat.end(); ++it)
    {
        if (it->second.size() == 2 * (n-1))
            wyn.push_back(it->first);
    }
    sort(wyn.begin(),wyn.end());
    cout << wyn.size() << '\n';
    for (int i = 0; i < wyn.size(); ++i)
        cout << wyn[i] << '\n';

    return 0;
}

Z góry dziękuję za pomoc i poświęcony czas!

1
komentarz 3 lutego 2023 przez Benek Szeryf (91,110 p.)
Zamiast plain text wybierz C++, to się kod pokoloruje.
komentarz 3 lutego 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
zrobione
komentarz 4 lutego 2023 przez Whistleroosh Maniak (56,980 p.)
Ciekawe zadanie. Mam jedynie pomysł na małe przyspieszenie. Miejscem w którym tracimy najwiecej czasu jest zapisywanie dla kazdego napisu krawędzi które obejmuje. Jak startujemy dfs'a z jakigoś wierzchołka, jesteśmy w v i przechodzimy to u to napis (nazwijmy go s1) od korzenia do v i od korzenia do u (nazwijmy s2) rózni się jedną literką. Dodatkowo krawędzie które obejmuje s2 to te same krawędzie które obejmuje s1 + jedna nowa. Dobrze więc gdyby szybko dało się skopiować te krawędzie obejmowanie przez s1 do s2. Można skorzystać z optymalizacji bitowej i tego że jest maks 1000 wierzhołków. Krawędzie które obejmuje napis nie będziemy pamiętać na mapie setów, ale mapie tablic rozmiaru 16 typu int_64. Jeśli odwiedziliśmy krawędź x to zamieniamy x-ty bit w binarnej reprezentacji tych 16 liczb na 1. (wzieliśmy 16, bo 16*64 = 1024 > 1000). Teraz jak mamy wypełnioną tę tablicę dla napisu s1 i wypełniamy dla s2 to iterujemy się po tych 16 liczbach dla s1 i robimy operacje OR z liczbami z tablicy dla s2. Tym samym zbiliśmy czas kopiowania obejmowanych krawędzi z liniowego od ilości wierzchołków do stałego (o stałej 16)
komentarz 4 lutego 2023 przez Whistleroosh Maniak (56,980 p.)

Chodzi mi o to, żeby zaimplementować coś jak bitset i skorzystać z tego, że operacje na typach elementarnych działają błyskawicznie. Tylko nie wiem jak zaimplementowany jest bitset, czy korzysta z int64 czy np. int32 (a lepiej korzystać z 64)

komentarz 4 lutego 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
I to ma O(N^2 * 16)?
komentarz 4 lutego 2023 przez Whistleroosh Maniak (56,980 p.)
Chyba tak. Dotychczas tylko raz spotkałem się z zadaniem w którym taka optymalizacja pomogła, ale to było jakieś zadanie z codeforces

1 odpowiedź

0 głosów
odpowiedź 29 listopada 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
edycja 29 listopada 2023 przez pasjonat_algorytmiki
Wróciłem dzisiaj do tego zadania, omówię rozwiązanie, które wchodzi na 100pkt.

Pierwsze spostrzerzenie jest dość oczywiste.

1 - Poprawny szablon musi zaczynać się w jakimś liściu i iść ścieżką do jakiegoś innego wierzchołka. Gdyby tak nie było, to krawędź od liścia do rodzica liścia nie byłaby użyta. Co więcej możemy zacząć z dowolnego liścia i puścić algorytm dfs do znalezienia wszystkich kandydatów na wynik. Trzeba tutaj uważać, żeby nie trzymać w dfs-ie całego napisu, dlatego robimy drzewo trie. Ukorzeniamy je w liściu i napełniamy to drzewo trie idąc dfs-em od liścia. Zauważmy, że będziemy mieć maksymalnie około n kandydatów na wynik.

No to mamy n kandydatów i trzeba umieć jakoś sprawdzić, który jest poprawnym szablonem. Zrobimy to w dwóch fazach. Chcemy dla każdego wierzchołka w drzewie trie naliczyć vector par (a,b), które mówią nam, że ciąg na ścieżce od wierzchołka a do wierzchołka b w oryginalnym drzewie z treści zadania ma taką samą wartość jak nasz wierzchołek w drzewie trie na ścieżce od korzenia drzewa trie do niego. Żeby to naliczyć, skorzystamy z tego, że n <= 2000, czyli możemy z każdego wierzchołka w drzewie z treści zadania puścić dfs-a, bo będziemy mieć wtedy o(n^2). Tak wiec robimy puszczamy dfs-a z każdego wierzchołka. Powiedzmy, że puściliśmy go aktualnie od wierzchołka x. W dfs-ie trzymamy aktualny wierzchołek w oryginalnym drzewie, oraz jaki numer ma ścieżka od x do y, (gdzie y, to aktualny wierzchołek w dfs-ie). Idziemy tylko wtedy, gdy dana literka ma krawędź w drzewie trie. Tak więc pozbyliśmy się za pomocą drzewa trie niepotrzebnego kopiowania napisów, wszystko mamy szybciutko.

Mamy policzone dla każdego wierzchołka w drzewie trie jakie ścieżki w oryginalnym drzewie mają taką samą wartość jak ścieżka z korzenia drzewa trie do niego, teraz musimy jakoś sprawdzić, czy da się całkowicie pokryć drzewo tymi ścieżkami. Zrobimy to korzystając z programowanie dynamicznego na drzewie. Dodajemy w wierzchołkach a i b, odejmujemy 2 w wierzchołku lca(a,b), gdzie lca(a,b) to najniższy wspólny przodek wierzchołków a i b. Przetwarzając it-y wierzchołek dp[v] = suma dp dzieci + to co dodaliśmy / usuneliśmy. Zauważmy, że krawędź do rodzica jest pokryta wtedy i tylko wtedy, gdy dp[v] > 0. Jeżeli dla wszystkich wierzchołków nie licząc korzenia dp[v] > 0, to drzewo da się pokryć takim szablonem, w przeciwnym razie nie.

Pozostaje pytania jaką ten algorytm ma złożonność. Kandydatów na wynik mamy około n, każdego przetwarzamy w o(n), czyli przetworzenie kandydatów ma o(n^2). Taką samą złożonność ma znalezienie kandydatów. Pozostaje pytanie co z lca par wierzchołków a i b oraz jak odtworzyć wynik. Limity zadania pozwalają nam na o(n^2* log n), czyli lca można znajdywać klasycznie jump pointerami w o(log n) oraz skoro mamy n kandydatów i każdy ma długość co najwyżej n-1, to możemy wrzucić wszystkie wyniki (w stringach) na seta / na vector i posortować i wypisać w kolejności posortowanej. Takie rozwiązanie ma o(n^2 * lg n) i jak najbardziej wchodzi na 100pkt, za bardzo dużym zapasem. Da sie to usprawnić do o(n^2) korzystając z tego, że nie trzeba liczyć lca za każdym razem oddzielnie w logu, tylko skoro mamy n <= 2000, to możemy dfs-em spreprocesować sobie lca dla każdej pary albo oddzielnie, albo wtedy gdy naliczamy te wszystkie pary (a,b). A jeśli chodzi o wypisywanie wyniku to drzewo trie daje nam porządek alfabetyczny wiec wydaje się, że można poprostu dfs-em w o(n^2) znaleźć wszystkie wyniki, tylko jest jeden problem, że jak pasuje szablon x, to szablon równy x, tylko w odwróconej kolejności też jest szablonem, wiec możemy zbudować już pod koniec drugie drzewo trie, na które dodamy wszystki oryginalne szablony spełniające wynik i wszystkie odwrócone szablony. W ten sposób możemy puścić dfs-a na takim drzewie trie i dostaniemy o(n^2).

Więcej o drzewach trie można zobaczyć np. tutaj:

- https://www.youtube.com/watch?v=MBIe1gBiWwY

- https://www.youtube.com/watch?v=RDRJpl-jUG4

- Było też takie zadanie na oi Midas.

Więcej o lca i jump pointerach:

- zadanie randka oi

- zadanie komiwojażer bajtazar oi

- zadanie odwiedziny oi

- https://www.youtube.com/watch?v=4tzs0brpbWA

- https://www.youtube.com/watch?v=IRMdwFMJ_NI&t=2041s

- https://www.youtube.com/watch?v=ub1E2lSuafc

- https://www.youtube.com/watch?v=x3pf0aiWgnI

 

Jeżeli kogoś interesuje ta sztuczka z dodawaniem 1 w liściach i odejmowaniem 2 w lca, to było takie zadania z ceoia, bardzo polecam: https://oj.uz/problem/view/CEOI17_oneway

Mam nadzieję, że rozwiązanie jest dobrze opisane.

Podobne pytania

0 głosów
1 odpowiedź 128 wizyt
0 głosów
1 odpowiedź 135 wizyt
pytanie zadane 23 czerwca 2023 w Algorytmy przez pasjonat_algorytmiki Pasjonat (19,540 p.)

92,626 zapytań

141,485 odpowiedzi

319,841 komentarzy

62,006 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...