|
Vaatleme arvutit, mille ainus mälu on pinu ja mille protsessori käsustikus on kaks operatsiooni:
Kirjutada oma tavalise arvuti jaoks programm, mis saab positiivse täisarvu N ja väljastab käskude Eesmärk on muidugi optimeerida genereeritud koodi efektiivsust, kuigi generaatori efektiivsus tuleb ka ainult kasuks. Näide: N = 4: PS. See on meelelahutus, mitte küsimus, mille vastust mulle millekski tegelikult vaja oleks. |
|
Mäletan, kui Ahto Ateena IOI-l pärast võistlust selle ülesande nuputamiseks andis. Minu leitud rekurrentne seos a[i]-de arvutamiseks oli eeltoodud Ahto lahendusest veidi kohmakam, aga see-eest leidsin viisi, kuidas optimaalse lahenduse otsimise asümptootilist ajakulu pisut vähendada. Pannes enda idee Ahto rekurrentsiga kokku, sain nüüd järgmise algoritmi.
Põhimõtteliselt on siin Ahto algoritm lihtsalt "tagurpidi" pööratud: me ei hakka järjendit läbi vaatama mitte "ülevalt alla" (iga i jaoks tema tegureid läbi vaadates), vaid "alt üles" (kui saame mingi a[i] väärtuse lõplikult teada, siis uuendame ka i kordsete väärtusi), nagu Eratosthenese sõela puhul. Võit tuleb sellest, et "ülevalt alla" algoritm peab iga i jaoks läbi vaatama hulga arve j, mis ei ole i tegurid. Kui "ülevalt alla" algoritmi ajakulu on Tõepoolest, iga i kohta tehakse vähem kui n div i sammu järgmiste a[i]-de uuendamiseks (sest i on teguriks ülimalt n div i arvule vahemikus 1...n), seega selliste sammude arv kokku ei ole suurem kui n div 2 + n div 3 + n div 4 + ... + n div n < n(1/1 + 1/2 + 1/3 + ... + 1/n). Sulgudes on harmoonilise rea osasumma, mis teadagi kasvab võrdeliselt Kui nüüd jälle "ülevalt alla" vaatenurgale üle minna, siis selle ajahinnangu tõestamisega tõestasime järgmise tulemuse: arvude Muidugi n*log(n) ajakulu on endiselt väga suur võrreldes väljundiga, mis on O(log n), nii et võib-olla on olemas kiirem algoritm optimaalse lahenduse leidmiseks. |
|
Järgnev Ruby-jupp leiab lühimad dupmul-jadad, kui sisend ei ületa väärtust 1037.
Mõnevõrra suuremate sisenditega saab majandada, kui enne väljundi genereerimist teha niimoodi:
Aga mis dünaamiline programmeerimine see siis enam on? Pealegi läheb suurte sisendite korral arvutamine aeglaseks:
Reaalses maailmas võiks veel kaaluda mitmesuguseid ettearvutamise-lähenemisi. Näiteks on võimalik iga dupmul-jada pikkusega N kodeerida N + 1 bitiga; kui me jadad indekseerimise hõlbustamiseks oktabiti-piirini joondame, saame 64 kibibaidi sisse mahutada jadad kuni sisendini 14100. Veel on võimalik tulemuste asemel salvestada rekursiooniotsuseid. Kui me rekursiooniotsusena salvestame väiksema teguri või eriväärtuse 0 dekrementimise tähisena, saab 64 kibibaidi sisse mahutada rekursioonijadad kuni sisendini 65535. (Kuna väiksem tegur ei ületa kunagi ruutjuurt, mahub see 16-bitise sisendi korral 8 biti sisse. 0 ei ole kunagi väiksem tegur.) ... siis on veel see häda, et suurte sisendite korral võib pinuarvutil mälu ja/või pinupesabitid otsa saada. Samas, tont seda pinuarvutit teab, äkki ta hoiab tegelikult ainult astendajaid.
(Dec 21 '09 at 12:09)
dig
Ma ei suutnud leida põhjendust, miks peaks olema piisav vaadelda ainult selliseid N tegurdusi, kus väiksem tegur on algarv. Tegin siis katse ja tuli välja, et näiteks N=1221 korral see ei olegi piisav...
(Jan 02 '10 at 00:32)
Ahto Truu ♦♦
|
|
Kuna paistab, et selle ülesandega keegi enam ei tegele, käin välja oma lahenduse:
Põhjenduseks, miks see algoritm alati optimaalse lahenduse leiab, paneme tähele, et pinuprotsessori käsustikus pole mingit võimalust pääseda magasinis sügavamal olevate elementide kallale, alati saab toimetada ainult tipmise elemendiga. See tähendab, et Esiteks me võime alguses dubleerida esialgse Teiseks me võime alustada sellest, et tõstame esialgse Kuna eeltoodud programm need võimalused Omaette uurimisteema saame, kui hakkame mõtlema sellele, et |
|
Jõudsin sisuliselt sama lahenduseni kui einz, aga algoritm on iteratiivne ilma stackita.
EDIT: #10#13 -> sLineBreak 2
Ei saa jätta norimata, et
(Dec 12 '09 at 22:43)
Ahto Truu ♦♦
|
|
Selline oleks minu lahendus. Kasutab ära põhimõtteliselt N-i kahendesitust.
|
|
Lisan siis ka teise lahenduse mis töötab arvu tegurdamisel algalarvudeks. See lahendus peaks olema optimaalne. Aste tegurdatakse algalarvudeks ja arv tõstetakse järjest neisse astmetesse (Arvu järjest astendamine on võrdne arvu astendamisega nende astmete korrutisega). Vaatleme igat algarvu tõsmist kui mingi pinus oleva arvu iseseisvat astendamist, kuna see tehe ei sõltu sellest mis astmes algne arv on või mis astmesse me tulemuse võtame. Kui arvu algalarvulisse astmesse tõstmine oleks optimaalselt lahendatud, siis oleks ka antud lahendus optimaalne. Arvu algarvulisse astmesse tõstmine on lahendame järgmiselt: Kui tegu on algarvulise kahest erineva astmega N vaatleme arvu astmesse N võtmist kui arvu korrutamiseks iseendaga astmes N - 1. Mingi arvu algalarvulisse kahest erinevasse astmesse tõstmiseks antud ülesandes peab vähemalt üks teguritest olema üks, et lõplik aste oleks paaritu, järelikult on tegu vajaliku ja seega optimaalse tehtega. Arvu võtame astmesse N - 1 rekursiivselt sama moodi nagu eelpool kirjeldatud. Rekursioon lõppeb, kui arvu on vaja võtta ruutu, mis tähendab tehetepaari "DUP MUL" ja on optimaalne. Kuna arvu algalarvu tõstmine on lahendatud ainult optimaalsete tehetega, on ka terviklahendus optimaalne.
Parandasin erti mainitud vea, naturaalarvu ajasin tõesti algarvuga segi.
(Dec 18 '09 at 20:56)
einz
Esiteks, funktsioonis
(Dec 20 '09 at 21:43)
Ahto Truu ♦♦
Teiseks, tundub, et mõte on olnud, et
(Dec 20 '09 at 21:44)
Ahto Truu ♦♦
1
Kolmandaks, selle programmi aluseks olev idee pole õige. Näiteks N=33 korral leiab kahendesitusel põhinev lahendus 12 tehtega, aga selline agar tegurdaja 14 tehtega avaldise.
(Dec 20 '09 at 21:47)
Ahto Truu ♦♦
See lahendus ei ole optimaalne, kuna ta üritab sisendit ainult tegurdada. Eksisteerib N=pq väärtusi, kus cost(seq(pq - 1)) + 1 < cost(seq(p) + seq(q)). Võib isegi juhtuda, et nende taga on mingisugune ilus seaduspärasus. (Tähistused: seq mäpib täisarvu lühimaks DUPMUL-jadaks, cost mäpib DUPMUL-jada selles leiduvate DUPMUL-paaride arvuks ehk pooleks jada pikkusest.)
(Dec 21 '09 at 12:16)
dig
|


Muidugi on astmenäitaja kahendesitusel põhinev lahendus igaks praktiliseks juhuks hea küllalt ja selle konstrueerimine on lisaks ka efektiivne ja kergesti mõistetav. Aga siiski pole see alati optimaalne. Näiteks N=15 korral annab see tulemuseks 12-käsulise jada
DUP DUP MUL DUP DUP MUL DUP DUP MUL MUL MUL MUL, aga võimalik on ka 10-käsulineDUP DUP MUL MUL DUP DUP MUL DUP MUL MUL. Kes kirjutaks programmi, mis genereerib alati optimaalse koodi? Kes oskab põhjendada, et see väidetavalt optimaalne ongi parim võimalik?