Sunteți pe pagina 1din 25

RMQ. LCA.

Parcurgere Euler

Alexandru Neagu
Faculty of Computer Science, Iași
Cuprins

1. Problema RMQ și cum se rezolvă optim


2. Conceptul de LCA
3. Ce este o parcurgere Euler?
4. Cum folosim parcurgerea Euler și problema RMQ pentru aflarea LCA-ului?
5. Ce alte metode de aflare a LCA există?
6. Probleme interesante cu LCA
Problema RMQ

Cerință : Se da un vector cu N elemente. Răspundeți la M întrebări de tipul "Care este elementul minim
din intervalul [x,y]?"
Problema RMQ

Cerință : Se da un vector cu N elemente. Răspundeți la M întrebări de tipul "Care este elementul minim din
intervalul [x,y]?"

Prima idee : Pentru fiecare întrebare dintre cele M, parcurgem intervalul [x,y] element cu element pentru
a calcula minimul. Complexitatea de timp în acest caz este O(M * N). Dezavantaj : Program foarte lent.
Problema RMQ

Cerință : Se dă un vector cu N elemente. Răspundeți la M întrebări de tipul "Care este elementul minim din
intervalul [x,y]?"

Prima idee : Pentru fiecare întrebare dintre cele M, parcurgem intervalul [x,y] element cu element pentru
a calcula minimul. Complexitatea de timp în acest caz este O(M * N). Dezavantaj : Program foarte lent.

A doua idee : Putem încerca să implementăm un Arbore Indexat Binar sau un Arbore de Intervale,
structuri de date bine cunoscute pentru rezolvarea acestor tip de probleme. Complexitatea de timp pentru
acest algoritm este O(M * log(n)). Dezavantaj : Deși complexitatea de timp este foarte bună, ne poate
afecta faptul că pentru fiecare întrebare trebuie să parcurgem structura de date. În plus, implementarea unui
arbore de intervale este destul de lungă și se pot comite greșeli foarte ușor!
Problema RMQ
Cerință : Se dă un vector cu N elemente. Răspundeți la M întrebări de tipul "Care este elementul minim din intervalul
[x,y]?"

Idee faină : Putem încerca să rezolvăm această problemă cu ajutorul programării dinamice. Datorită faptului
că fiecare interval poate fi privit mereu ca reuniunea a două subintervale (nu neapărat disjuncte) care au
lungimile puteri de 2, am putea încerca să facem o preprocesare. Fie dp[i][j], elementul minim din subșirul care
începe pe poziția i și are lungimea 2j. Efectiv, dp[i][j] este elementul minim din secvența [i, i+2j-1]. Cum fiecare
secvență care are lungimea 2j poate fi împărțită cu ușurință în două secvențe de lungimea 2j-1, se observă
tranziția dp[i][j] = min(dp[i][j-1], dp[i + 2j-1][j-1]). Cazurile de bază sunt dp[i][0], pe care le inițializăm cu a[i].
Putem calcula aceste informații în complexitate de timp O(NlogN). După ce facem etapa de precalculare, ținând
cont că fiecare interval poate fi descompus în doua subintervale care sunt puteri de 2 și profitând de proprietățile
funcției min, putem calcula rezultatul pentru fiecare interval [x,y] în complexitate O(1) folosindu-ne de cea mai
mare putere de 2 mai mică sau egală cu lungimea intervalului. Complexitate Finală : O(NlogN)
Problema RMQ
Cerință : Se dă un vector cu N elemente. Răspundeți la M întrebări de tipul "Care este elementul minim din
intervalul [x,y]?"
Problema RMQ

Link cu o implementare a problemei : https://pastebin.com/UypGnnSW

Resurse suplimentare faine :

1) https://www.youtube.com/watch?v=0jWeUdxrGm4
2) https://cp-algorithms.com/data_structures/sparse-table.html
Conceptul de LCA
Cerință : Se dă un arbore G și m întrebări de tipul (x, y). Aflați LCA (Lowest Common Ancestor) al
nodurilor x și y.

Ce este LCA? Pentru oricare două noduri (x, y) din arborele nostru, LCA-ul lor este acel nod z care are
proprietatea că aparține atât drumului de la x spre rădăcină, cât și drumului de de la y spre rădăcină!
Dacă sunt mai multe astfel de noduri, LCA-ul este cel mai de jos!
Conceptul de LCA
Cerință : Se dă un arbore G și m întrebări de tipul (x, y). Aflați LCA (Lowest Common Ancestor) al
nodurilor x și y.
Conceptul de LCA
Cerință : Se dă un arbore G și m întrebări de tipul (x, y). Aflați LCA (Lowest Common Ancestor) al
nodurilor x și y.

Ce trebuie să știm pentru a rezolva problema? Conceptul de Parcurgere Euler (Euler Tour)!
Ce este o parcurgere Euler?

Chiar dacă sună stufos, o parcurgere euler nu este nimic altceva decât o parcurgere dfs a arborelui
nostru G (începând cu rădăcina ca fiind nodul 1) în care adăugăm fiecare nod pe care îl vizităm într-o
listă pe care o numim euler (posibil de mai multe ori!) : o dată la începutul apelului recursiv (când vizităm
pentru prima dată nodul) și de fiecare dată când revenim din apelul recursiv al altui nod! Pe lângă
această listă cu noduri, se mai stochează și adâncimea (depth[x]) și prima apariție în lista Euler (first[x]) a
nodului x.
Ce este o parcurgere Euler?
Cum folosim parcurgerea Euler și problema RMQ pentru aflarea LCA-ului?

După ce am făcut parcurgerea dfs a arborelui și am extras informația necesară pentru lista Euler,
observăm că putem defini problema LCA a nodurilor (x, y) în felul următor : Dacă considerăm prima
apariție a nodului x, respectiv prima apariție a nodului y în lista Euler, LCA-ul este nodul care posedă cel
mai mic nivel și se află în lista euler între cele două poziții discutate anterior! Acest lucru se poate calcula
ușor aplicând tehnica RMQ discutată anterior. Complexitatea Finală : O(NlogN)!
Cum folosim parcurgerea Euler și problema RMQ pentru aflarea LCA-ului?

Link cu o implementare a problemei LCA : https://pastebin.com/YkbUHW7q

Alte resurse faine :

1) https://cp-algorithms.com/graph/lca.html
2) https://www.youtube.com/watch?v=GWXf3vVtf-c
Ce alte metode de aflare a LCA există?

Pentru cei interesați, există și alte metode mai simple de aflare a LCA-ului care deși nu au o complexitate
la fel de bună ca implementarea cu RMQ, funcționează în majoritatea cazurilor în practică! Cea mai
populară metodă de acest gen este Binary Liftingul, care se bazează pe calcularea pentru fiecare nod x a
următoarei informații : dp[x][i] - nodul care se află cu 2i nivele mai sus decât nodul x dacă mergem din
părinte în părinte. Când calculăm dp[x][i], ne folosim de următoarea tranziție dp[x][i] = dp[ dp[x][i-1] ][i-
1]. Pentru acest algoritm, mai este necesar să ne folosim de adâncimea fiecărui nod (depth[x]) !
Folosindu-ne de aceste informații, putem calcula LCA al nodurilor x și y în următoarea manieră :
Încercăm să urcăm din nodul x cât mai mult folosindu-ne de infomațiile stocate în dp, atâta timp cât
nodul în care urcăm nu este la un depth mai mic decât nodul y.
Ce alte metode de aflare a LCA există?

Resurse utile pentru această metodă :

1) https://cp-algorithms.com/graph/lca_binary_lifting.html
2) https://www.youtube.com/watch?v=oib-XsjFa-M
3) https://www.youtube.com/watch?v=dOAxrhAUIhA
Ce alte metode de aflare a
LCA există?
Problema 1-trees and queries

Am ales să discutăm această problemă fiindcă este necesară abordarea conceptului de distanță între
două noduri. Definim distanța dintre două noduri (x, y) din arbore ca fiind cel mai scurt drum de la nodul
x la nodul y! Pentru a rezolva această problemă putem observa că putem distinge două cazuri : Fie ne
folosim de muchia “specială” adăugată în arbore, fie alegem să nu o folosim deloc. În cel de al doilea caz
este ușor de văzut că ce trebuie să facem este să calculăm distanța dintre nodurile a și b și să verificăm
dacă are aceeași paritate cu k (alegem cel mai scurt drum și în caz că mai avem nevoie să parcurgem
muchii pentru a ajunge la k muchii parcurse alternăm între ultimele 2 noduri din parcurgere). Defapt
pentru primul caz, se procedează în mod analog doar că distanța se calculează după formula min(dist(a,
x) + 1 + dist(y, b), dist(a, y) + 1 + dist(x, b)). Practic în acest mod ne forțăm să folosim muchia (x,y) și să
vedem cel mai scurt drum dacă facem asta.
Problema 1-trees and queries

Ne mai rămâne de aflat un singur lucru. Cum aflăm în modul cel mai optim distanța dintre două noduri x
și y?
Problema 1-trees and queries

Ne mai rămâne de aflat un singur lucru. Cum aflăm în modul cel mai optim distanța dintre două
noduri x și y?

Răspuns : Se observă că atunci când căutăm distanța minimă, este mereu cel mai optim să trecem
prin nodul z care este LCA al celor 2 și se află mai sus pe arbore! Cu această observație crucială,
folosindu-ne de informația din tabloul depth care ne zice la ce nivele sunt nodurile în arbore,
deducem formula dist = depth[x] + depth[y] - 2 * depth[z]
Problema 1-trees and queries

Implementarea soluției : https://pastebin.com/2Tg2A0fK

De ținut minte funcția dist! Se utilizează foarte des în practică.


Problema Minimum spanning tree for each edge

În continuare voi folosi următoarele notații :

1) MST General - MST-ul clasic pe care îl construim fără nici o restricție


2) MST Forțat - MST-ul adaptat corespunzător la fiecare query

În primul rând, observăm că pentru fiecare muchie “forțată” MST-ul care se obține este unul foarte similar
cu cel original în care nu punem nici o restricție de muchii! Cu ceva mai multă analiză, putem observa că de
fiecare dată din MST-ul original este ștearsă o singură muchie care este înlocuită cu muchia (x, y) de la
fiecare query! Mai mult decât atât, observăm că pentru a se păstra proprietatea de arbore, muchia ștearsă
trebuie să fie neapărat pe drumul de la nodul x la nodul y în MST-ul original. Făcând această observație,
putem să conchidem că mereu este optim să alegem muchia cu cel mai mare cost care se află pe drumul
dintre x și y în MST-ul original și să o înlocuim cu muchia (x, y)! Folosind algoritmii noștri clasici de aflare a
LCA, problema de aflare a muchiei de cost maxim care se află pe drumul de la x și y se transformă în :
aflarea muchiei maxime dacă urcăm din părinte în părinte de la x spre LCA și de la y spre LCA!
Problema Minimum spanning tree for each edge

Folosind algoritmii noștri clasici de aflare a LCA, problema de aflare a muchiei de cost maxim care se află
pe drumul de la x și y se transformă în : aflarea muchiei maxime dacă urcăm din părinte în părinte de la x
spre LCA și de la y spre LCA! Pentru această problemă trebuie să folosim conceptul de binary lifting pe
care îl folosim în mai multe forme : up[node][i] - nodul în care ajungem dacă urcăm din părinte în părinte
de 2i ori și big[node][i] - muchia de cost maxim dacă urcăm 2i muchii din nodul node! Pentru calcularea
efectivă a acestor informații se folosesc recurențele : up[node][i] = up[up[node][i-1]][i-1] și big[node][i] =
max(big[node][i-1], big[up[node][i-1]][i-1]). Detalii suplimentare despre implementare se regăsesc în
soluție : https://pastebin.com/Tm4nediK
Probleme Suplimentare

1) Fools and Roads


2) A and B and Lecture Rooms
3) Duff in the Army
4) Tree Queries

S-ar putea să vă placă și