Jimi si nedávno kúpil nový herný počítač a jeho štúdium tým značne utrpelo. Je veľmi ťažké učiť sa, keď máte na výber z toľkých dobrých hier. Preto si vytvoril rozvrh, v ktorom má každú hodinu označenú ako pracovnú alebo hernú. Po čase hrania ale Jimiho prestalo baviť zabíjanie príšer a rozhodol sa splniť veľkú úlohu – zachráni princeznú ukrytú na hmlistom ostrove.
Hra však nepodporuje ukladanie počas plnenia misie a preto Jimi nemôže pracovať, kým nezachráni princeznú. Nechce si ale veľmi meniť rozvrh, preto len vyškrtne niektoré pracovné hodiny z rozvrhu tak, aby si vytvoril dostatočne dlhý súvislý úsek herného času na záchranu princeznej. Jeho štúdium je však už teraz riadne zanedbané, a preto chce obetovať čo najmenej zo svojho pracovného času.
Jimiho rozvrh vyzerá ako jedna dlhá postupnosť znakov P a H, ktoré označujú pracovné a herné hodiny.
Jimi si vypočítal, že na záchranu princeznej bude potrebovať $$c$$ hodín hry.
Vašou úlohou je zistiť, koľko najmenej hodín práce vie vyškrtnúť z rozvrhu tak, aby mal aspoň $$\mathbf{c}$$ hodín hry v jednom kuse.
Môžete predpokladať, že herných hodín je v rozvrhu vždy aspoň $$c$$.
V prvom riadku je číslo $$c$$ – počet hodín potrebných na záchranu princeznej. V druhom riadku je rozvrh – reťazec znakov P a H dĺžky $$n$$.
Pre vstupy platí $$1 \leq c \leq n \leq 1\,000\,000$$.
Vypíšte jedno číslo – najmenší počet hodín práce, ktoré treba vyškrtnúť z rozvrhu. Výstup ukončite znakom nového riadku.
Input:
3
PPHHPPPHPHPHPPP
Output:
2
Upravený rozvrh vyzerá takto: PPHHPPPHHHPPP.
Input:
4
HHPPPHHPPHHHPH
Output:
1
Input:
2
HHPPHPP
Output:
0
Predstavíme si dve riešenia: riešenie hrubou silou a optimálne riešenie.
Príklad vieme vyriešiť veľmi jednoducho vyskúšaním všetkých možných začiatkov záchrany: Spočítame, koľko hodín práce by musel Jimi preskočiť, keby začal so záchranou práve v $$i$$-tu hodinu. Zo všetkých $$i$$ vyberieme také, kedy je počet preskočených hodín práce minimálny.
Počet preskočených hodín spočítame tak, že v cykle prejdeme vstupné pole od $$i$$-tej hodiny, pričom si budeme počítať počet prejdených P a H. Ak dosiahneme počet H rovný $$c$$, dosiahli sme potrebný počet hracích hodín a počet P je počet obetovaných pracovných hodín. Stačí teda počet P porovnať s doterajším minimom a ak je menší, našli sme nové minimum.
Keď od hodiny $$i$$ po koniec rozvrhu ostáva menej ako $$c$$ hodín, vypíšeme najmenší počet vynechaných P.
Keďže pre každý začiatok môžeme prejsť až na koniec poľa, časová zložitosť je $$O(n^2)$$.
Môžeme si všimnúť, že v predošlom riešení robíme množstvo práce viackrát. Ak totiž poznáme počet P-čok (označme ho $$p_i$$) v úseku začínajúcom na $$i$$, tak vieme, že počet P-čok v úseku začínajúcom na $$i+1$$ nemusí byť veľmi odlišný. Mohli by sme teda zvýšiť $$i$$ na $$i+1$$ a nezačať počítať P a H od $$i+1$$ ale rovno od $$i + c + p_i$$ (teda odtiaľ, kde sme skončili prechod poľa pre predošlé $$i$$), kým nedosiahneme potrebný počet H-čok.
My budeme postupovať podobne, akurát teraz nebudeme skúšať všetky začiatky, ale prejdeme cez všetky konce.
Budeme sa teda vždy pozerať na časť poľa, ktorá je určená začiatkom a koncom[^1]. Tiež si udržiavame počet P a počet H v tejto časti poľa.
Začneme so začiatkom aj koncom na prvom políčku poľa. V každom kroku cyklu posunieme koniec úseku o jedno políčko ďalej. Kým je počet H v našom úseku väčší alebo rovný $$c$$, posúvame začiatok úseku doprava. Tým dosiahneme najkratší úsek s týmto koncom, ktorý má aspoň $$c$$ H-čok. Keďže je najkratší, má aj minimálny počet P-čok. Počet P-čok porovnáme s medzivýsledkom a zapamätáme si menší.
Po prechode poľom vypíšeme minimum.
Každé políčko poľa sme navštívili raz koncom a najviac raz začiatkom, teda celková časová zložitosť je $$O(n)$$.
#include <iostream>
#include <algorithm>
using namespace std;
int main () {
int c;
string vstup;
cin >> c;
cin >> vstup;
int n = vstup.size();
int zaciatok = 0;
int H = 0, P = 0;
int vysledok = n;
for (int koniec = 0; koniec < n; koniec++) {
if (vstup[koniec] == 'P') {
P++;
} else {
H++;
}
while (H >= c && zaciatok < koniec) {
vysledok = min(P, vysledok);
if (vstup[zaciatok] == 'P') {
P--;
} else {
H--;
}
zaciatok++;
}
}
cout << vysledok << endl;
return 0;
}
Korešpondenčný seminár z programovania zastrešuje občianske združenie Trojsten.
Trojsten, o.z.
FMFI UK, Mlynská dolina
842 48 Bratislava
Programátorská súťaž pre základoškolákov
Materiály a úlohy na výučbu programovania
Intenzívny programátorský zážitok v lete