Zadanie

Ak nevieš programovať, nezúfaj! Môžeš sa to naučiť a ešte za to získať body, ktoré sa ti budú počítať namiesto tejto úlohy.

Stačí, že pôjdeš na stránku Programátorskej Liahne (liahen.ksp.sk). Keď budeš riešiť sadu loops_cpp, bodmi, ktoré získaš, si môžeš nahradiť riešenie tejto úlohy. Stačí ak na spodku tejto stránky odovzdáš pdf-ko s prezývkou, ktorú používaš na Liahni.

Ak máte akékoľvek otázky ohľadom tejto úlohy, napíšte Adamovi na [email protected]

Bratia Artur a Boris medzi sebou radi súťažia a tak tomu bolo aj teraz. Keď Artur čítal knihu, všimol si zaujímavé slovo a prehlásil, že určite obsahuje viac častí1 takých, že začínajú spoluhláskou. Boris mu hneď neveril a potreboval si túto domnienku overiť, lebo podľa neho bolo viac častí začínajúcich samohláskou. Keďže bratov zaujíma aj o koľko častí kto v tejto súťaži vyhral, potrebujú nájsť presný počet častí slova začínajúcich samohláskou a aj častí začínajúcich spoluhláskou. Keďže nechcú hľadať všetky takéto časti slova len za pomoci papiera, potrebujú vašu pomoc, aby im to šikovne zrátal program.

Úloha

Vašou úlohou je napísať program, ktorý na vstupe načíta slovo dĺžky práve \(n\) a na jeho základe vypočíta výsledné body súrodencov. Slovo môže obsahovať len malé a veľké písmená 26 písmennej anglickej abecedy.

Formát vstupu

Na prvom riadku sa nachádza jedno číslo \(n\) (platí \(1 \leq n \leq 10^6\)), dĺžka slova. Na nasledujúcom riadku je reťazec dĺžky \(n\) z písmen anglickej abecedy. Môže obsahovať malé aj veľké písmená.

Formát výstupu

Na jediný riadok výstupu vypíšte počet substringov (súvislých podúsekov slova) začínajúcich samohláskou a počet substringov začínajúcich spoluhláskou oddelené medzerou.

Hodnotenie

Je 5 sád vstupov. Platia v nich nasledujúce obmedzenia:

Číslo sady 1 2 3 4 5
\(1 \leq n \leq\) \(500\) \(1000\) \(100\,000\) \(750\,000\) \(10^6\)

Výsledky v posledných sadách môžu byť veľmi veľké čísla. Chcete preto použiť 64 bitové premenné (napr. long long v C++, Int64 v Pascale), lebo do obyčajnej 32 bitovej premennej sa nemusia zmestiť. Ak programujete v pythone, nemusíte sa tým zaoberať, on to vyrieši za vás.

Príklad

Input:

5
prask

Output:

3 12

*Substringy zašínajúce samohláskou sú len 3 a to ask, as, a. Spoluhláskou začína 12 substringov a to p, r, s, k, prask, pras, pra, rask, ras, ra, pr a sk.

Input:

15
aekeocjekolskrj

Output:

66 54

  1. Časť slova, nazývaná aj substring, predstavuje akýkoľvek interval dĺžky od \(1\) po \(n\) nachádzajúci sa v slove (stringu).↩︎

Teória

Aby sme vedeli spočítať skóre súrodencov, potrebujeme spôsob, ako určiť, či je písmeno samohláska, alebo spoluhláska. Bohužiaľ túto vlastnosť písmen počítač nedokáže rozpoznať sám od seba a musíme mu najskôr povedať ako taká samohláska vyzerá. Aby sme si to zjednodušili zapamätáme si len ako vyzerajú samohlásky a necháme program rozhodnúť či kontrolované písmeno je alebo nie je samohláska. Kontrola prebehne tak, že program prehľadá pole samohlások a pri zhode s kontrolovaným písmenom okamžite rozhodne, že písmeno je samohláska, inak po dokončení prehľadávania rozhodne, že písmeno je spoluhláska (pretože to nebola žiadna zo samohlások). Takýmto spôsobom program nemusí vôbec vedieť o tom, ako vyzerajú spoluhlásky a stále dostaneme správne riešenie. Ak by sme pri riešení implementovali aj separátne pole pre spoluhlásky, tak by program v pomalších jazykoch nezbehol na plný počet bodov.

Riešenie hrubou silou

Najpriamočiarejšie riešenie je skontrolovať, akým písmenom sa začína každý možný substring v slove pomocou spôsobu, ktorý sme si vysvetlili v teórii. Všetkých substringov je rádovo \(n^2/2\), lebo pre písmeno na indexe \(i\) existuje \(n-i\) substringov, ktoré ním začínajú a \(i\) substringov, ktoré sa ním končia. Takéto riešenie má časovú zložitosť \(O(n^2)\) a v časovom limite vyrieši len prvé \(2\) sady.

Za takéto riešenie bolo možné získať \(40\) bodov.

n = int(input())
a = input()
s = [0, 0]

# zname samohlasky
sam = ["a", "e", "i", "o", "u", "y", "A", "E", "I", "O", "U", "Y"]

# spocitame zaciatky spol. a sam. vsetkych substringov v slove
for i in range(n):
    for j in range(n):
        if a[i:j+1].startswith(a[i]):
            if sam.__contains__(a[i]):
                s[0] += 1
            else:
                s[1] += 1
print(*s)

Vzorové riešenie

Ak chceme zlepšiť časovú zložitosť, musíme znížiť počet krokov, ktoré program vykoná. Môžeme si všimnúť, že počet substringov začínajúcich písmenom na pozícii \(i\) je \(n - i\). Od začiatku substringu \(n_i\), ktorý poznáme, sa substring vie predlžovať len do jedného smeru a to len presne \(n - i\) krát, lebo tam slovo končí a substring sa už nemá ako predĺžiť. Počet predĺžení od písmena vieme pre každé písmeno v slove zistiť v konštantnom čase, takže celú úlohu vieme vyriešiť v lineárnom čase \(O(n)\). Pamäťová zložitosť je \(O(1)\), lebo si v pamäti držíme len konštantný počet samohlások a výsledky.

Keby sme si počet samohlások označili \(s\), časová zložitosť by bola \(O(ns)\). Keby sme si tieto samohlásky pamätali vo vhodnej štruktúre, vedeli by sme o každom písmene zistiť, či je to samohláska v konštantom čase. Takouto štruktúrou je napríklad unordered_set v C++, alebo set v pythone. Toto vylepšenie však na plný počet bodov nebolo potrebné.

#include <iostream>
using namespace std;

int main()
{
    int n;
    string a;
    cin >> n;
    cin >> a;
    long long out[] = {0,0};

    // zname samohlasky
    string sam = "aeiouyAEIOUY";

    // pre kazde pismeno pripocitam pocet substringov
    for(int i=0; i<n; i++) {

        // je pismeno samohlaska?
        bool result = false;
        for(int j=0; j<sam.size(); j++) {
            if (a[i] == sam[j]) result = true;
        }
        
        if (result) {
            out[0] += n - i;
        } else {
            out[1] += n - i;
        }
    }

    cout << out[0] << " " << out[1] << endl;
    return 0;
}
n = int(input())
a = input()
s = [0, 0]

# zname samohlasky
sam = ["a", "e", "i", "o", "u", "y", "A", "E", "I", "O", "U", "Y"]

# pre kazde pismeno pripocitam pocet substringov
for i in range(n):
    if sam.__contains__(a[i]):
        s[0] += n - i
    else:
        s[1] += n - i
print(*s)

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ť.