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:
= int(input())
n = list(map(int,input().split())) # načítanie vstupu
cisla
# usporiadam si čísla, nech sú rovnaké pri sebe
cisla.sort() = 0;
vysledok
for i in range(1,n): # zistím počet rôznych čísel
if(cisla[i-1] != cisla[i]):
+= 1;
vysledok
+= 1 # treba ešte pripočítať posledné číslo
vysledok
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;
>> n;
cin
<int> cisla;
set
for(int i = 0;i < n;i++){
>> cislo;
cin .insert(cislo);
cisla}
// keďže v set nemôže obsahovať rovnaké čísla,
// veľkosť setu čísla bude počet rôznych čísel na vstupe
<< cisla.size() << endl;
cout }
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ť.