Zadanie

Táto úloha je programátorská. Ako svoje riešenie odovzdaj program vo svojom obľúbenom jazyku a automaticky sa dozvieš, koľko si dostal/a bodov. Ak si takýto typ úloh ešte nikdy neriešil/a skús sa pozrieť ako by mal vyzerať ideálny program. Ak zaťiaľ programovať nevieš, ale chcel/a by si vedieť možeš skúsiť náš python tutoriál.

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

Čas Vianoc sa blíži a preto už je najvyšší čas napísať si zoznam darčekov! Merlin si teda sadol a začal písať. Napísal si \(n\) darčekov, ktoré by veľmi chcel niekedy na Vianoce dostať. Keďže to je ale huncút, skopíroval ho za seba až \(n\)-krát (pre istotu, nech sa niektorý darček náhodou neprehliadne). Nanešťastie bolo týchto darčekov strašne veľa, a tak sa Merlinovi rodičia rozhodli, že mu každý rok dajú len jeden darček z jeho zoznamu. Aby to ale malého Merlina nezarmútilo, spravili to nasledovne. Každý rok mu dajú darček, ktorý ho poteší ostro viac, ako ten minulý rok! Pomôžte Merlinovim rodičom zistiť, koľko rokov im takto vystačí Merlinov zoznam?

Úloha

Na vstupe dostanete zoznam čísel \(a\), ktorý má dĺžku \(n\). Tento zoznam potom za seba \(n\)-krát skopírujeme. Podpostupnosť tohoto zoznamu dokážeme získať tak, že niektoré prvky vymažeme a ostatné necháme tak. Vašou úlohou je zistiť dĺžku najdlhšej rastúcej podpostupnosti takto vzniknutej postupnosti. Pre rastúcu postupnosť platí, že pre každé \(i\), \(a[i-1] < a[i]\).

Formát vstupu

Na prvom riadku dostanete číslo \(n\) \((1 \leq n \leq 10^5)\), počet Merlinových želaných darčekov. Na druhom riadku sa nachádza \(n\) čísel. Číslo \(a_i\) \((1 \leq a_i \leq 10^9)\) určuje, ako veľmi sa Merlinovi daný darček páči.

Formát výstupu

Na výstup vypíšte jedno číslo, dĺžku najdlhšej stúpajúcej podpostupnosti, ktorú dostaneme tak, že zopakujeme pôvodný Merlinov zoznam \(n\)-krát.

Hodnotenie

Existuje 5 sád vstupov, pre ktoré platia nasledovné obmedzenia:

Číslo sady \(n\) \(max(a_i)\)
1 \(10\) \(10^5\)
2 \(100\) \(10^5\)
3 \(1\,000\) \(10^5\)
4 \(100\,000\) \(10^5\)
5 \(100\,000\) \(10^9\)

Príklad

Input:

3
3 1 2

Output:

3

Z prvej kópie zoberieme \(1\) a \(2\) a z druhej zoberieme \(3\).

Input:

6
3 1 4 1 5 9

Output:

5

Výsledná postupnosť bude \(1, 3, 4, 5, 9\).

Prvé pozorovanie

Hľadaná podpostupnosť je rastúca, teda sa v nej určite nemôžu nachádzať 2 rovnaké čísla. Keď skopírujem postupnosť niekoľkokrát za seba, nepridajú sa žiadne nové čísla. Teda hľadaná podpostupnosť môže mať dĺžku najviac počet rôznych čísel v pôvodnej postupnosti.

Konštrukcia

Rôznych čísel v pôvodnej postupnosti bude vždy najviac \(n\), teda aj najväčší možný výsledok bude tiež \(n\). Potom môžeme každé z čísel z výslednej podpostupnosti brať z inej kópie pôvodného zoznamu. Možné optimálne riešenie môžeme dostať napríklad tak, že si rôzne čísla nachádzajúce sa v zozname usporiadame od najmenšieho po najväčšie. Toto bude najdlhšia neklesajúca podpostupnosť skopírovaného zoznamu a získať ju môžeme napríklad tak, že prvý prvok zoberieme z prvej kópie zoznamu, druhý z druhej atď.

Ako to naprogramovať?

Keďže nám stačí zistiť len dĺžku najdlhšej rastúcej podpostupnosti, stačí len nájsť počet rôznych čísel, ktoré sa nachádzajú na vstupe. Keď si čísla v pôvodnom zozname utriedime, rovnaké čísla sa budú nachádzať vedľa seba, teda pomocou jedného for cyklu vieme zistiť počet rôznych čísel v zozname. Toto riešenie má časovú zložitosť \(O(n \log_{2}{n})\). Takéto riešenie naprogramované v Pythone by mohlo vyzerať napríklad takto:

n = int(input())
cisla = list(map(int,input().split())) # načítanie vstupu

cisla.sort() # usporiadam si čísla, nech sú rovnaké pri sebe
vysledok = 0;

for i in range(1,n): # zistím počet rôznych čísel
    if(cisla[i-1] != cisla[i]):
        vysledok += 1;

vysledok += 1 # treba ešte pripočítať posledné číslo

print(vysledok)

Druhá možnosť je využiť knižničnú dátovú štruktúru set. Set si vieme predstaviť ako množinu, v ktorej sa každý prvok môže nachádzať najviac raz. Do tejto množiny vieme vložiť nejaký prvok, odstrániť z nej nejaký prvok alebo zistiť, či sa v nej nachádza nejaký prvok, všetko v čase \(O(\log_{2}{n})\). Rozdiel medzi dátovými štruktúrami set a unordered set je ten, že prvky v sete sú usporiadané, teda ich vieme prechádzať od najmenšieho po najväčší. Preto má unordered set lepšie časové zložitosti a to \(O(1)\) pre pridávanie, odstránenie aj zistenie, či sa prvok nachádza v množine.

Keďže v sete sa každé číslo nachádza len raz, stačí nám len každé číslo doň v čase \(O(\log_{2}{n})\) (alebo \(O(1)\) v prípade unordered set) pridať a potom vypísať veľkosť tohto setu. Toto riešenie má časovú zložitosť \(O(n \log_{2}{n})\) pri použití dátovej štruktúry set (alebo \(O(n)\) pri použití štruktúry unordered set), lebo každé číslo na vstupe treba do setu pridať práve raz. Takéto riešenie v C++ by mohlo vyzerať napríklad takto:

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n,cislo;
    cin >> n;
    
    set<int> cisla;
    
    for(int i = 0;i < n;i++){
        cin >> cislo;
        cisla.insert(cislo);
    }
    
    // keďže v set nemôže obsahovať rovnaké čísla,
    // veľkosť setu čísla bude počet rôznych čísel na vstupe
    cout << cisla.size() << endl; 
}

Pamäťová zložitosť je v oboch prípadoch \(O(n)\), lebo si stačí pamätať len čísla z pôvodného zoznamu.

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