Informatică, întrebare adresată de ionutg38, 9 ani în urmă

Eșuînd în a-și regăsi adevărata identitate, Brush se refugiază în magicul tărâm al arborilor. Arbotra o sună și îi dă următoarea problemă: se dă un arbore cu N noduri, o rădăcină fixată, și M întrebări de forma: câte perechi neordonate de noduri pot forma, luând noduri doar din subarborele nodului X (inclusiv pe X).

Cerința
Ajutați-o pe Brush să răspundă cât mai repede la intrebările lui Arbotra.

Date de intrare
Fișierul de intrare arbrush.in va conține pe prima linie 3 numere naturale, numărul N, M și Root, separate de un spațiu. Următoarele N-1 linii vor conține două numere naturale a și b (între 1 și N), separate de un spațiu, cu semnificația că există muchie între nodurile a și b. Ultima linie a fișierului de intrare (linia N+1) va conține M numere separate de căte un spațiu, acestea vor semnifica cele M întrebari cerute de Arbotra.

Date de ieșire
Fișierul de ieșire arbrush.out va conține M linii, aflându-se răspunsul la fiecare întrebare cerută (în ordinea cerută în fișierul de intrare).

Restricții și precizări
2 ≤ N, M ≤ 27040
1 ≤ Root ≤ N
Uituc de fel, Arbotra poate pune aceeași întrebare de mai multe ori.



Exemplu
arbrush.in

8 4 1
1 2
2 3
2 5
5 6
6 7
6 8
1 4
6 4 5 2
arbrush.out

3
0
6
15
Explicație
Pentru prima întrebare, răspunsul este 3, deoarece în subarborele nodului 6 există 3 noduri: 6, 7, 8, cu care pot forma perechile {6, 7}, {7, 8}, {6, 8}

Pentru a doua întrebare răspunsul este 0, deoarece subarborele lui 4 are doar un nod, pe el înșusi și nu se pot forma perechi de câte două noduri.

Pentru a treia întrebare răspunsul este 6, deoarece în subarborele nodului 5 există 4 noduri: 5, 6, 7, 8, perechile formate sunt {6, 8}, {5, 6}, {7, 8}, {5, 8}, {5, 7}, {6, 7}

Pentru a patra întrebare răspunsul este 15.

INDICATII DE REZOLVARE
Indicații de rezolvare


Varianta 1: Complexitate: O(N*M) – se obtin doar 40 de puncte

Putem face câte o parcurgere DF la fiecare query, astfel numărăm toate nodurile din subarborele lui X, având grijă să nu mergem spre rădăcină. Fie S numărul de noduri din subarborele lui X, răspunsul este C2SCS2

Varianta 2: Complexitate: O(N+M) – se obtin 100 puncte

Ținem o dinamică D[x] = câte noduri există în subarborele lui x, calculăm vectorul D cu o parcurgere DF, actualizând costurile la întoarcere. Recurența

D[x] = D[fiu1] + D[fiu2] + … D[fiuK] + 1, unde K este numărul de fii a lui x.

Răspunsul la fiecare întrebare este C2D[x]CD[x]2. Pentru fiecare răspuns la întrebare complexitatea este O(1).

Pe mine ma intereseaza varianta 2.


blindseeker90: Ar fi mai usor de utilizat clase pentru a strange datele. Nu am avut timp sa citesc varianta dinamica. Singura metoda prin care pot sa o rezolv acum este cu matrice adiacenta a graficului prin cautare depth first simpla, varianta de 40 de puncte
blindseeker90: Cand am timp, o sa scriu codul
ionutg38: Aia cu 40 de puncte nu ma intereseaza ca am facut-o
ionutg38: Oricum, multumesc! Ma interesa varianta dinamica. Daca nu ai timp, o sa incerc sa ma ocup eu.
blindseeker90: E si tu acuma. Vrei la toate 100?
ionutg38: Nu e vorba de asta, dar pentru 40 am rezolvat-o
ionutg38: Mi se pare normal sa-ti spun ca am rezolvat-o asa, ca sa nu muncesti degeaba
blindseeker90: Glumeam ma :). Imi place de tine ca esti ambitios.
ionutg38: Am inteles si gluma, dar am raspuns ca sa lamurim lucrurile
ionutg38: Oricum, iti multumesc mult! Esti un baiat de nota 10 CU +

Răspunsuri la întrebare

Răspuns de blindseeker90
2
Fisierul respectiv face o parcurgere a arborelui prin referinte descendente, de tip tata. Mai intai spune parintele si apoi copilul.
Programarea dinamica de obicei se formeaza de la coada la capat. Incepi cu cea mai simpla unitate si ajungi dupa aceea la cea mai complexa.
Asta inseamna ca arborele ar trebui parcurs prin referinte ascendente, de la nodurile terminale pana la radacina.
Transformarea din referinte descendente in referinte ascendente ia destul de multe operatii, pentru ca trebuie sa iei terminalele de la cel cu cea mai mica eticheta la cel cu cea mai mare, altfel risti sa faci cicluri in citire(citesti parinte, apoi fiu, apoi acelasi tata, apoi iar fiul initial)

In arborele tau de aici, incepi cu 3, apoi cu 4, 7,8 si apoi il iei pe 6, care devine nod terminal dupa inlaturarea lui 7 si 8.
La final o sa ai doi vectori t si pt(parinte terminal) care o sa arate asa
t:3 4 7 8 6 5 2 
pt:2 1 6 6 4 2 1

Odata aflati acesti 2 vectori faci urmatorii pasi per cautare
1) Setezi un vector de cost cu 1(fiecare nod se cunoaste pe el insusi)
2) verifici pentru un nod parinte daca este un succesor al nodului X la care cauti(altfel nu are sens sa faci calcule partiale)
3) incepi sa faci calcule partiale

Fisierul CPP are o gramada de alte functii de le-am mai facut eu comentate pentru diverse teste. Dar oricum, ma tem ca acea transformare din referinte descendente in referinte ascendente o sa faca programul incet.
Oricum, si dimensiunile vectorilor sunt mici, doar de 20, este ceva demonstrativ cam cum s-ar face pornind de la terminale. 
Anexe:

ionutg38: Toate testele depasesc limita de timp
blindseeker90: Da, trebuie facut pana la urma cu referinte descendente, de la radacina in jos. E mai greu algoritmul si de parcurs si de facut partea de programare dinamica. Algoritmul asta e foarte bun, dar daca modul de reprezentare al datelor este altul, pana fac conversia care apropo este de tip N*M, se duce toata performanta
ionutg38: Lasa, nu te mai chinui. Incerc sa-l fac singur. Multumesc mult!
ionutg38: #include <fstream>
using namespace std;
ifstream cin("arbrush.in");
ofstream cout("arbrush.out");
int n,m,r,T[20],D[20],X[20],x,y,nod,nr;
int C[20][20];
void dfs(int k)
{
D[k]=1;
for(int i=1;i<=n;++i)
if(T[i]==k)
dfs(i);
if(X[nod])
for(int i=1;i<=n;i++)
if(T[i]==nod)
D[k]+=D[i];
}

void comb(int n)
{
for(int i=0;i<=n;++i)
for(int j=0;j<=i;++j)
if(i==0||j==i)
C[i][j]=1;
else
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
ionutg38: int main()
{
int i;
cin>>n>>m>>r;
T[r]=0;
X[0]=1;
for(i=1;i<n;i++)
{
cin>>x>>y;
T[y]=x;
X[T[y]]++;
}
comb(n);
for(i=1;i<=m;i++)
{
cin>>nod;
dfs(nod);
nr=0;
for(int j=1;j<=n;++j)
if(D[j])
++nr;
cout<<C[nr][2]<<'\n';
for(int j=1;j<=n;++j)
D[j]=0;
}
return 0;
}
ionutg38: Uite o-ncercare, dar nu e ceea ce trebuie
ionutg38: Am impartit sursa-n doua pt ca nu-ncapea aici
Alte întrebări interesante