www.agora.ro
Din GInfo nr. 2

SOLUȚIILE problemelor propuse

ACM, chiar dacă este un concurs organizat pentru studenți, diferă de concursurile destinate elevilor de liceu prin faptul că se concurează pe echipe, iar evaluarea are loc în "timp real". Sperăm că soluțiile prezentate vă vor fi utile în scopul pregătirii pentru olimpiade. Soluțiile publicate mai jos aparțin echipei câștigătoare a Universității București. Puteți cunoaște mai bine membrii acestei echipe citind interviul realizat cu ei în acest număr al revistei.

P029901: Țara Confuziei

  Harta Țării Confuziei seamănă foarte mult cu un automat finit nedeterminist (un graf orientat în care muchiile sunt etichetate). Dacă se încearcă găsirea soluției prin metoda backtracking, complexitatea este exponențială.

Codificăm N cu 1, E cu 2, S cu 3 și V cu 4. În matricea a păstrăm harta astfel: a[i,j,0] reprezintă numărul intersecțiilor în care se poate ajunge din intersecția i, urmând direcția j. Pentru 0<kŁa[i,j,0], a[i,j,k] reprezintă o intersecție în care se poate ajunge din i pe direcția j.

Vom folosi un vector b având ca dimensiune numărul max al intersecțiilor. Vectorul b este vectorul caracteristic al submulțimii de intersecții la care se poate ajunge la momentul curent, moment corespunzător numărului de caractere analizate din șirul ce trebuie testat. Deci b[i]=1 dacă și numai dacă la momentul curent se poate ajunge în intersecția i. Inițial (dacă s-au citit 0 caractere) toate componentele lui b, cu excepția celei corespunzătoare intersecției de plecare, au valoarea 0.

La citirea unui nou caracter chÎ{'N','S','E','V'} din șirul de intrare ce trebuie testat, noul b va fi calculat astfel:

 c[i]Źfalse, " i=1,...,max 
 Úț for i=1,max do 
 ł if b[i]=1 
 ł then for t=1,a[i,ch,0] do 
 ł c[a[i,ch,t]]Źtrue 
 Àț 
 bŹc 

Se observă că aceaste calcule se fac în timp polinomial pentru toate intersecțiile, iar în final se verifică dacă pe poziția din vectorul b, corespunzătoare intersecției destinație, apare sau nu 1.

Listing: FUZZY.PAS

P029902: Numărul majoritar

  Problema a fost prezentată în Gazeta de Informatică nr. 1/1999, în articolul "Alegeri prezidențiale". Va fi prezentată mai jos o altă metodă de rezolvare.

Cel mai simplu algoritm liniar presupune să folosim un vector indexat după toate numerele posibile, în care să memorăm pentru fiecare număr frecvența aparițiilor sale.

Deoarece un asemenea vector ar ocupa 16 GB, folosim un artificiu ce constă în a considera pentru fiecare număr cei 4 octeți din reprezentarea sa.

Folosim o matrice frecventa[0..3,0..255], inițializată cu 0, cu următoarea semnificație:

frecventa[i,j]= de câte ori octetul i din numerele

din șirul de intrare este egal cu j.

Afirmăm că dacă există un număr majoritar în șir, atunci el este compus din unul din octeții ce se repetă cel mai des pe poziția 0 (fie el majoritar[0]) plus unul din octeții ce se repetă cel mai des pe poziția 1 (fie el majoritar[1]) etc. Afirmația are la bază faptul că dacă există un număr majoritar, atunci el este unic.

Într-adevăr, dacă alegem un octet o[i] care nu se repetă cel mai des, el nu poate face parte dintr-un număr majoritar. De asemenea, dacă pentru un i=0,1,2,3 există mai mulți octeți care se repetă cel mai des, nu are importanță pe care îl alegem, deoarece nu va exista un număr majoritar. De aceea vom alege pentru fiecare i=0,...,3 un octet majoritar pe linia i a matricei frecventa și vom forma din ei un număr candidat la a fi majoritar. Rămâne să verificăm dacă acest număr este într-adevăr majoritar, ceea ce se realizează verificând dacă el apare de mai mult de n/2 ori în șirul de intrare.

Observație:

Pentru a citi o singură dată din fișierul de intrare, împreună cu calculul matricei frecventa, facem o copie a acestui fișier pe care o folosim la a doua parcurgere și pe care apoi o "distrugem".

Listing: NUMAR.PAS

P029903: Rețele sigure

  Definiția unei subrețele maximale este de fapt definiția unei componente biconexe a grafului de calculatoare.

Folosim algoritmul cunoscut de determinare a componentelor biconexe ale unui graf neorientat; complexitatea în timp a algoritmului este de ordinul O(n*m), unde n este numărul de vârfuri și m numărul de muchii (vezi Gazeta de Informatică nr.2/1996). În plus, ordonăm calculatoarele în fiecare subrețea și ordonăm subrețelele după primul calculator.

Listing: RETELE.PAS

P029904: Drumuri pe autostradă

  Definim un graf orientat G1, ale cărui vârfuri corespund punctelor de pe hartă; două vârfuri sunt adiacente dacă punctele corespunzătoare sunt puncte libere adiacente. Unul din vârfurile corespunzătoare orașelor îl notăm cu start, iar pe celălalt îl notăm cu stop. Definim costul unui arc (x,y) ca fiind:

- 1+costul punctului corespunzător vârfului x, dacă x±start;

- 2, dacă x=start.

Se observă că toate arcele au costul strict pozitiv.

Problema se reduce la determinarea lungimii drumului minim de la start la stop, a punctelor care se află pe cel puțin un drum minim și a numărului de drumuri minime.

Lungimea drumurilor minime de la start la toate vârfurile o determinăm folosind algoritmul Dijkstra, modificat astfel încât să păstreze toți tații pentru fiecare vârf (nu doar unul).

Fie acum un graf orientat G2, care are aceleași vârfuri ca și G1 și în care arcele sunt legăturile (fiu,tata) determinate de algoritmul lui Dijkstra. Graful G2 este aciclic.

Determinarea vârfurilor care sunt pe cel puțin un drum minim de la start la stop în G1 se reduce la determinarea vârfurilor la care se poate ajunge plecând din stop în G2.

Determinăm numărul drumurilor minime de la start la stop în G1 care este egal cu numărul drumurilor de la stop la start în G2. Pentru fiecare vârf istart notăm numărul drumurilor de la i la start în G2 cu drumuri(i) și avem:

drumuri (i)=suma(drumuri(j)), pentru toți j, tați ai lui i, dacă start nu este tată al lui i, respectiv

drumuri (i)=1+suma(drumuri(j)), pentru toți j±start cu j tată al lui i, dacă start este tată al lui i.

Calculăm drumuri(stop) cu ajutorul acestei formule, folosind metoda programării dinamice.

Timpul de executare este dominat de timpul de executare al algoritmului Dijkstra, iar în condițiile concursului nu a fost nevoie de o implementare Dijkstra mai bună de O(n2), unde n este numărul de vârfuri. Să observăm că grafurile G1 și G2 au o proprietate importantă: fiecare vârf este adiacent cu cel mult 4 vârfuri, deci numărul de arce e majorat de o constantă înmulțită cu numărul de vârfuri. Ținând cont de aceasta, algoritmul Dijkstra poate fi implementat în timpul O(n* log n).

Listing: DRUMURI.PAS

P029906: Rețea de comunicare

  Cea mai complicată parte din această problemă este citirea și memorarea intrării (jumătate din enunț se referă doar la formatul de introducere a datelor). Pentru fiecare comutator am folosit o structură SG care conține:

l - comutatorul legat la intrarea L;

lc - ieșirea comutatorului memorat în l, legată de intrarea L;

d - comutatorul legat la intrarea D;

dc - ieșirea comutatorului memorat în d, legată de intrarea D;

s - starea pinului de control al comutatorului.

De o mare importanță este observația că nu pot exista cicluri (din enunț COM2>COM1 pentru fiecare conectare). Din acest motiv se pot calcula direct valorile dorite pentru diferite ieșiri ale comutatoarelor. Aceasta se face prin identificarea repetată a tatălui până când se ajunge la o intrare neconectată, iar apoi se verifică semnalul pentru aceasta.

Listing: RETEA.PAS

 

P029907: Combinații

  Prima etapă a rezolvării constă în adaptarea șirului citit la lungimea cerută, adică înlocuirea sa cu un șir de această lungime, astfel încât rezultatele să fie aceleași. Dacă șirul e prea scurt, acesta se completează cu cea mai mică literă considerată (adică 'A'), ceea ce e valabil și pentru inferior și pentru superior. Dacă șirul e prea lung, atunci pentru inferior este suficientă trunchierea șirului, deoarece șirul trunchiat e mai mic (lexicografic) decât șirul inițial; însă pentru superior șirul trebuie incrementat în urma trunchierii. Incrementarea are loc exact ca la numerele naturale:

aZ+1=aa; xzCzzz+1=xzDAAA; zzzzz+1=(null).

Urmează prelucrarea șirului obținut. Atât șirul inferior, cât și șirul superior trebuie să se încadreze literă cu literă între cel mai mic șir și cel mai mare șir (în ordine lexicografică) cu k litere. De exemplu, pentru k=4 aceste șiruri sunt ABCD și wxyz. De aceea vom face 3 teste:

  • - "testul sus" verifică depășirea celui mai mare șir;
  • - "testul jos" verifică depășirea ("în jos") a celui mai mic șir;
  • - "testul de ordine" verifică ordonarea strict crescătoare a literelor.

Pentru determinarea inferiorului:

"Testul jos" se termină în sens negativ (nu are loc depășire) dacă ajungem la o literă strict mai mare decât litera corespunzătoare din șirul cel mai mic, deoarece în continuare va fi aplicat "testul de ordine".

Dacă ajungem la o literă pentru care "testul sus" dă rezultat pozitiv (depășire), atunci copiem cel mai mare șir de la poziția respectivă până la sfârșitul șirului. "Testul de ordine" pentru poziția curentă este automat verificat, deoarece litera precedentă a verificat "testul sus".

În sfârșit, dacă litera curentă nu verifică "testul de ordine", atunci decrementăm subșirul format din literele anterioare (păstrând ordinea literelor!) și apoi copiem cel mai mare șir din poziția curentă până la sfârșit. Decrementarea este posibilă dacă a fost trecut "testul jos".

Pentru determinarea superiorului, lucrurile sunt ceva mai simple:

"Testul jos" este automat verificat de "testul de ordine".

"Testul sus" este necesar doar dacă prima literă a șirului este mai mare sau egală cu prima litera a celui mai mare șir; în caz contrar este asigurată posibilitatea incrementării șirului, păstrând ordinea literelor și "testul sus" va fi suplinit de "testul de ordine".

Dacă litera curentă nu verifică "testul de ordine", atunci incrementăm subșirul format din literele anterioare cu ultima literă dublată (păstrând ordinea literelor și verificând "testul sus") și apoi completăm șirul cu litere consecutive.

Algoritmul este evident liniar în k, deci practic are timp constant (kŁ52).

Listing: COMBIN.PAS

[cuprins]