Algoritmus je založený na myšlienke prehľadávania grafu do hĺbky. Prehľadávanie do hĺbky je rekurzívna procedúra s jediným parametrom - vrcholom v. Tento vrchol označíme za už prehľadaný a rekurzívne voláme tú istú procedúru pre všetkých ešte neprehľadaných susedov vrchola v. Volajme týchto susedov potomkami vrchola v. Nazvime hlavnou každú hranu, ktorá vedie z predka do niektorého z jeho potomkov, a hrany, ktoré nie sú hlavné, volajme spätné.
Pre naše účely priradíme naviac pri prehľadávaní každému vrcholu v poradové číslo cv označujúce, koľkáty v celkovom poradí bol vrchol v prvýkrát objavený. Zároveň každú ešte neorientovanú (obojsmernú) hranu orientujeme ("zjednosmerníme") smerom od vrchola v. Orientáciu už orientovaných hrán nemeníme.
Ukážeme, ako sa dá tento algoritmus použiť na nájdenie mostov v grafe. Aby sme zistili, či je hrana z u do v most, potrebujeme overiť, či sa dá dostať z v do u po hranách rôznych od hrany (u,v). Ak sa dá, hrana (u,v) mostom byť nemôže. Naopak, ak sa nedá, hrana (u,v) je most. Keďže každý most je hlavnou hranou, predpokladajme, že v je potomok u, teda prehľadávacia procedúra pre v bola spustená v prehľadávacej procedúre pre u. Nech w je vrchol s najmenším číslom cw taký, do ktorého sa dá dostať z vrchola v po orientovaných hranách.
Ak je hrana (u,v) most, pre každý vrchol w' objavený volaním prehľadávacej procedúry z v platí cw'>=cv. Zároveň žiaden z vrcholov, do ktorých sa vieme prehľadávaním z v dostať (nepoužívame hlavné, t.j. už orientované, hrany) nemohol byť v okamihu volania procedúry pre v objavený. Teda cw>=cv.
Naopak, predpokladajme, že cv<=cw. Označme P množinu prehľadaných vrcholov tesne pred spustením procedúry pre v a Q množinu vrcholov, ktoré objavíme touto procedúrou. Okrem hrany (u,v) nemôže viesť žiadna hlavná hrana z vrchola z P do vrchola z Q, ani naopak. (Ak by viedla z P do Q, vrchol v Q by bol už prehľadaný pred volaním procedúry, čo je spor. Ak by viedla z Q do P, táto hrana by nemohla byť hlavná, pretože vedie do už objaveného vrchola - opäť spor.) Ak cv<=cw, neexistuje žiadna spätná hrana z Q do P (inak by platilo cv>cw), a teda neexistuje žiadna hrana medzi P a Q okrem (u,v). Z toho vyplýva, že hrana (u,v) je most.
Zostáva nám ukázať, že hrany, ktoré nie sú mostami, sú orientované vhodne, t.j. že je možné prejsť z každého vrchola do ľubovoľného iného vrchola dodržujúc orientáciu hrán. Predpokladajme, že graf G nemá mosty. Nech u a v sú také vrcholy, že cu=1 a cv=2. Z predošlého vieme, že pre hlavnú hranu (u,v), ktorá nie je mostom, platí cv>cw, teda cw=1. Teda existuje orientovaný cyklus (postupnosť vrcholov v1,...,vk taká, že v1=vk a pre každé i=1,...,k-1 vedie z vrchola vi do vi+1 orientovaná hrana) prechádzajúci vrcholmi u, v. Označme S množinu vrcholov z cyklu. Pre množinu S platí, že sa vieme z každého do každého z jej vrcholov dostať po orientovaných hranách. Ak S obsahuje všetky vrcholy, ukázali sme, že orientácia grafu vyhovuje. V opačnom prípade nech x je vrchol mimo S taký, že existuje vrchol y v S, že z y vedie do x orientovaná hrana. Z x sa dá dostať po orientovaných hranách do niektorého vrcholu z S, pretože inak by bola hrana (y,x) most. Takže sa dá dostať aj z x do u, aj z u do x, a teda ak vrchol x pridáme do S, zostane zachovaná vlastnosť, že z každého do každého vrchola v S sa dá dostať. Induktívne môžme pridať do S všetky vrcholy, z čoho vyplýva, že navrhnutá orientácia G je vhodná.
Analogickú argumentáciu možno použiť aj keď sa v grafe G mosty nachádzajú. Nech G1 je (neorientovaný) graf, ktorý vznikne z G po odobraní všetkých mostov. Označme C1,...,Cl komponenty súvislosti G1 (z každého vrchola v Ci sa dá dostať do každého vrchola z Ci po hranách z G1, pre i=1,...,l). Existuje aspoň jeden komponent Ci, do ktorého viedol jediný most. (Ak si zostrojíme graf, v ktorom každému komponentu zodpovedá jeden vrchol a dva vrcholy sú spojené hranou práve vtedy, keď medzi zodpovedajúcimi komponentami v G vedie most, tento graf je súvislý a neobsahuje kružnice, musí to byť teda strom. Každý strom má aspoň jeden list.) Pre tento komponent možno použiť argumenty z predchádzajúceho odstavca, Ci z grafu vynechať a induktívne pokračovať v dôkaze pre ostatné Cj.
v
je v poli kriz
uložený
zoznam jeho susedných vrcholov. Teda každá hrana je v tomto poli uložená
dvojnásobne a tieto dve kópie na seba navzájom ukazujú pomocou smerníkov
dual
. Atribút aktiv
hovorí, či sa dá v tom smere po danej
hrane prechádzať (využíva sa pri orientovaní, keď povoľujeme len jeden z
dvoch možných smerov hrany). Pole sus[v]
uchováva počet susedov
vrchola v
, pole c[v]
obsahuje cv a v poli naj[v]
je uložené číslo cw. Najskôr pomocou procedúry prehladaj
podľa
vyššieuvedeného algoritmu orientujeme všetky hrany grafu, pričom mosty
úplne vymažeme (obidvom hranám nastavíme aktiv
na false
).
Potom vypíšeme všetky aktívne hrany.Pamäťová zložitosť algoritmu je O(M+N)=O(M). Časová zložitosť je zhodná so zložitosťou prehľadávania do hĺbky, teda tiež O(M) (po každej hrane prejdeme práve raz).
Kandidátov je zjavne najviac k-1. Budeme ich hľadať nasledovne: pre každý prvok p, ktorý sa v poli a aspoň raz vyskytuje, si budeme počítať jeho silu s[p]. Na začiatku položme silu všetkých prvkov rovnú 0. Silu prvkov budeme meniť takým spôsobom, aby v každom okamihu bola nenulová pre nanajvýš k-1 prvkov. Tieto prvky nazvime kandidátmi a označme ich K1,...,Kk-1.
Pri spracovávaní prvku a[i] môžu nastať tieto situácie:
Je zrejmé, že si netreba pamätať silu všetkých prvkov, stačí si pamätať sily k-1 kandidátov a to, ktoré prvky sú kandidátmi. Na to nám stačia dve polia veľkosti O(k).
Všimnime si teraz prvok P. Nech jeho výskyt A-krát spôsobil zníženie, B-krát zvýšenie. Opäť sporom. Ukážeme, že s[P]>0. Ak by mal prvok P na konci silu 0, znamenalo by to, že bolo aspoň A+B znížení - A-krát ho spôsobil prvok P, B iných znížení muselo prvku P znížiť silu na 0. Počet znížení je teda aspoň A+B=M>N/k, čo je spor s vyššie dokázaným tvrdením, že zvýšení je najviac N/k. Preto má prvok P na konci nenulovú silu. To je možné len tak, že bude na konci kandidátom.
Časová a pamäťová zložitosť: algoritmus vyžaduje dva prechody poľom, každý z nich je v čase O(k.N). Pamäťová zložitosť je O(k).
Blok pre bitový logický súčin skonštruujeme podobne ako blok pre logický súčet (OR, viď. ďalej). Na výpočet súčtu prvkov v množine môžeme použiť blok SHR(x), ktorý zisťuje hodnotu najnižšieho bitu čísla v registri x a zároveň register x celočíselne vydelí dvomi (viď. ďalej) a blok ADD(x,y), definovaný v príklade zo zadania.
Všetky popisované bloky pre správnu činnosť predpokladajú, že všetky použité pomocné registre sú pred vstupom do bloku vynulované. Pred výstupom z bloku sa tieto registre opäť vynulujú.
Blok SHL(x) vynásobí číslo v registri x dvomi. Potrebuje jeden pomocný
register p, do ktorého "presype" obsah registra x, potom obsah p
presýpa do x, pričom za každé zníženie registra p dvakrát zvýši obsah
registra x.
Blok SHR(x) celočíselne delí číslo v registri x dvomi. Má dva výstupy
označené 0 a 1, pričom výstup 0 sa použije, ak bolo
číslo v registri x pri vstupe do bloku párne a výstup 1, ak bolo
nepárne. Pracuje podobne ako blok SHL s tým, že najprv za každé
dve zníženia registra x raz zvýšime pomocný register a potom presýpame
obsah pomocného registra naspäť do x.
Blok OR(x,y) je najzložitejší. Jeho funkciou je priradiť do registra
x bitový OR čísel uložených v registroch x a y a zároveň vynulovať
register y. Budeme to robiť podobne ako vo vzorovom riešení krajského kola.
Pomocou blokov SHR odoberieme posledný bit zo zápisu oboch čísel
x,y. Ak je aspoň jeden z odobratých bitov jednotkový, pridáme na koniec
zápisu čísla v pomocnom registri p jednotku (t.j. p vynásobíme dvomi
pomocou bloku SHL a potom k nemu pripočítame jednotku), v opačnom prípade
pridáme na koniec p nulu (t.j. vynásobíme ho dvomi). Takto sa nám bude
v registri p postupne objavovať bitový súčet čísel x a y, avšak s
bitmi zapísanými v obrátenom poradí. Na koniec teda musíme obsah
registra p otočený zapísať do registra x, čo spravíme opäť odoberaním
posledného bitu zápisu p a jeho pridávaním na koniec zápisu x. Aby sme
vedeli, koľkokrát máme túto operáciu spraviť, počítame si počet platných bitov
registra p v pomocnom registri q.
Nakoniec nám zostáva popísať samotný stroj. Skladá sa z dvoch hlavných cyklov. V prvom cykle sa v registri R3 postupne počítajú kódy množín Si, po skončení i-teho cyklu R3 obsahuje kód si. Zároveň sa v každom prechode odoberie z množiny M jej najmenší prvok.
Odoberanie prvku sa robí v dvoch menších cykloch spolu s výpočtom kódu
množiny S'_{i-1}, ktorý bude uložený v registri R4.
Na začiatku si kód s_{i-1} skopírujeme z registra R3 aj do registra R4.
V prvom cykle
postupne delíme obsah registra R1 (t.j. kód množiny M) dvomi a obsah
registra R4 naopak násobíme dvomi, pričom si počítame počet prechodov cyklu
v registri R5. Keď sa nám nepodarí vydeliť obsah R1 dvomi bezo zvyšku,
narazili sme na najmenší prvok. V tomto okamihu máme v R4 kód S'i-1,
ktorý pomocou bloku OR pridáme k obsahu R3, čím nám v tomto registri
vznikne kód množiny Si. Na záver v druhom cykle po sebe upraceme, t.j.
vynásobíme obsah R1 takou mocninou dvojky, akou sme ho v prvom cykle
vydelili. Všimnite si, že týmto postupom sa nám automaticky vynuloval najnižší
nenulový bit registra R1, t.j. odobrali sme najmenší prvok množiny M.
Keď z M odoberieme posledný prvok, jej kód uložený v registri R1 sa vynuluje a riadenie prejde do druhého hlavného cyklu. V tomto cykle overíme, či číslo s uložené v registri R2, patrí do množiny, ktorej kód je uložený v registri R3. Hodnotu R3 stačí s krát vydelť dvomi a potom zistiť, či posledný bit registra je 1.