Zadanie

Matúš už vďaka vám našiel disk, ktorý má najlepší pomer ceny ku kapacite, ale stále nie je celkom spokojný. Momentálne je totiž spomenutý disk veľmi drahý. Po krátkej úvahe si Matúš uvedomil, že sa disky nevyrábajú v Európe, ale na Taiwane, a preto ich cena závisí od kurzu eura k taiwanskému doláru. Pohrabal sa v ekonomických zákutiach internetu a stiahol si vývoj kurzu na nasledujúcich \(n\) dní. Keďže potreboval disk ihneď, kúpil si ho na splátky priamo u taiwanskej spoločnosti.

Matúš každý deň spláca \(s\) eur, avšak ktorýkoľvek deň sa sa môže rozhodnúť doplatiť zvyšok sumy. Teraz už len potrebuje zistiť, ako dlho by mal platiť splátky a čakať so zaplatením zvyšku tak, aby dokopy zaplatil čo najmenej.

No a v súlade s jeho žgrlošským zmýšľaním prenechal Matúš túto úlohu lacnejšej pracovnej sile: vám!

Úloha

Na vstupe dostanete vývoj kurzu taiwanského doláru na najbližších \(n\) dní, cenu disku \(c\) v taiwanských dolároch a výšku splátky \(s\) v eurách. Matúš spláca každý deň \(s\) eur začínajúc prvým dňom, ktorý je na vstupe. Skutočnú sumu, ktorú dostane taiwanská spoločnosť vypočítame tak, že sumu, ktorú zaplatí Matúš v eurách vynásobíme kurzom na daný deň. Ak je kurz \(47\) a Matúš zaplatí \(2\) eurá, tak sa z celkovej sumy \(c\) odráta \(47\cdot 2=94\) taiwanských dolárov. Celková suma, ktorú dostane spoločnosť v taiwanských dolároch, môže byť aj vyššia, ako cena disku, keďže Matúš vie platiť iba celočíselné sumy.

Vašou úlohou je zistiť, v ktorý deň má Matúš zaplatiť zvyšok dlhu, aby dokopy zaplatil čo najmenej eur. Ak je takých dní viac, vypíšte ten najskorší, aby Matúš splácal čo najkratšie. V prípade, že sa mu oplatí čakať až do \(n\)-tého dňa, musí Matúš vyplatiť zvyšok sumy v tento deň. Matúš musí aj v posledný deň splácania zaplatiť aspoň \(s\) eur.

Formát vstupu

V prvom riadku vstupu sú tri kladné celé čísla \(n\), \(c\) a \(s\) (\(1 \leq n \leq 200\,000, 1 \leq c,s \leq 10^9\)) udávajúce počet dní pre ktoré poznáme kurz, cenu disku a výšku splátky. V nasledujúcom riadku je \(n\) celých čísel oddelených medzerou, kde číslo na pozícií \(i\) udáva kurz v \(i\)-tom dni.

Formát výstupu

Vypíšte jedno číslo – taký deň, v ktorom má Matúš doplatiť zvyšok sumy, aby dokopy zaplatil čo najmenej. Nezabudnite za ním vypísať koniec riadku.

Príklad

Input:

5 1000 2
3 47 190 50 30

Output:

3

V prvý deň zaplatí \(2\cdot 3=6\) dolárov, druhý deň \(2\cdot 47=94\) a tretí deň doplatí zvyšok \(5\cdot 190=950\).

Input:

4 200 10
3 2 10 20

Output:

3

V tretí deň zaplatí \(15\) eur, takže dokopy zaplatí 35 eur v hodnote \(10\cdot 3+10\cdot 2+15\cdot 10=200\) dolárov. Ak by čakal na najvyšší kurz vo štvrtý deň, zaplatil by dokopy \(40\) eur, pretože aj v posledný deň splácania musí Matúš zaplatiť aspoň \(10\) eur.

Input:

3 1000 1
100 10 1

Output:

1

Matúš sa môže rozhodnúť, že zaplatí celú sumu aj v prvý deň.

Úlohou bolo zistiť, v ktorý deň je najvýhodnejšie zaplatiť zvyšok dlhu.

Potrebujeme teda pre každý deň zistiť, koľko eur by Matúš celkovo zaplatil, ak by vyplatil zvyšok dlhu v tento deň. Potom nám stačí vybrať najskorší deň kedy je táto suma najnižšia.

Otázkou ostáva, ako počítať sumu v eurách, ktorú by zaplatil, ak by ukončil splácanie v \(i\)-ty deň. Táto suma sa skladá z dvoch častí:

Prvá časť je to, čo musel zaplatiť každý deň splácania – \(i\)-krát výška splátky, teda \(i \cdot s\).

Druhá časť musí pokrývať nesplatený zvyšok ceny v taiwanských dolároch. Tento zvyšok vypočítame ako rozdiel ceny disku a súčtu hodnôt všetkých doterajších splátok. Ak si teda označíme kurz \(i\)-teho dňa ako \(k_i\), zvyšok je \(c - s \cdot (k_1 + k_2 + ... + k_i)\).

Môžeme si všimnúť, že ak poznáme zvyšok v \(i\)-ty deň, ľahko vieme vypočítať zvyšok v \(i+1\) deň, a to tak že od zvyšku v \(i\)-ty deň odrátame hodnotu splátky v \(i+1\) deň – \(s \cdot k_{i+1}\)1.

Keď už poznáme zvyšok, ktorý treba pokryť, vydelíme ho kurzom na tento deň a získame sumu v eurách ktorú treba doplatiť. Nakoľko chceme platiť celočíselne, túto sumu ešte zaokrúhlime nahor.

Celkovú sumu, ktorú Matúš zaplatí, ak sa rozhodne doplatiť dlh v \(i\)-ty deň vieme teda vypočítať ako \[ i \cdot s + \left\lceil \frac{c - s \cdot (k_1 + k_2 + ... + k_i)}{k_i} \right\rceil\]

Keď poznáme celkovú sumu, stačí ju porovnať s doteraz najnižšou sumou ktorú sme vyrátali, a ak je lepšia, tak si uložíme číslo tohto dňa. Treba si dať pozor, aby sme výpočet zastavili potom, ako sa zvyšok stane záporným, lebo vtedy je už disk splatený.

Zložitosť

Ak postupujeme tak, ako sme popisovali vyššie, stačí nám v každom z \(n\) krokov prečítať hodnotu kurzu na tento deň a spraviť konštantne veľa jednoduchých výpočtov a porovnaní, ktoré majú konštantnú časovú zložitosť. Preto celková časová zložitosť bude \(O(n)\).

Lepšie sa to dokonca ani nedá, keďže na vstupe máme kurzy na \(n\) dní, ktoré musíme načítať2.

Pamäťová zložitosť je \(O(1)\), pretože si stačí pamätať len zvyšok, ktorý treba zaplatiť z predošlého dňa a kurz v daný deň.

#include <iostream>
#include <cmath>

using namespace std;

int main(){
    int n, cena, splatka, best = 1234567890, den = 1, kurz;
    cin >> n >> cena >> splatka;
    for (int i=0; i<n; i++){
        cin >> kurz;
        cena -= kurz * splatka; // kolko taiwanskych dolarov zostava zaplatit

        // kolko eur by zaplatil ak skonci tento den
        int teraz = (i + 1) * splatka;
        if(cena>0) teraz += ceil(cena/kurz);

        if (teraz < best){
            best = teraz;
            den = i + 1;
        }

        if (cena < 0) break;
    }
    cout << den << endl;
}
uses crt, math;
var n, cena, splatka, best, teraz, den, kurz, i:longint;
begin
    best := 1234567890;
    den := 1;
    read(n, cena, splatka);
    for i := 1 to n do
    begin
        read(kurz);
        dec(cena, kurz * splatka);

        teraz := i * splatka;
        if cena > 0 then inc(teraz, ceil(cena / kurz));

        if teraz < best then
        begin
            best := teraz;
            den := i;
        end;

        if cena < 0 then break;
    end;
    writeln(den);
end.

  1. Ak by sme pre každý deň zvlášť počítali súčet \((k_1 + k_2 + ... + k_i)\), potebovali by sme si kurzy pamätať v poli, no čo je horšie, výpočet by mohol trvať dlho. Pre \(n\) dní by sme postupne sčítavali \(1, 2, 3, \dots n\) čísel, teda celkovo by sme sčítali \(1 + 2 + 3 + \dots + n = \frac{n(n-1)}{2} = O(n^2)\) čísel. Takýto program by úspešne stihol vyriešiť vstupy s \(n\) nanajvýš \(10\,000\).

  2. Odhad zložitosti robíme všeobecne, teda uvažujeme najhorší možný prípad – ak je najvýhodnejšie doplatiť splátku v posledný deň, musíme načítať všetky kurzy.

Diskusia

Tu môžte voľne diskutovať o riešení, deliť sa o svoje kusy kódu a podobne.

Pre pridávanie komentárov sa musíš prihlásiť.