PO: Informatica Olympiade 2017-2018 Handreiking Wt Stedelijk Gymnasum s-hertogenbosch Wat is de Informatica Olympiade? De Nederlandse Informatica Olympiade (NIO) is een programmeerwedstrijd voor de bovenbouw van het Voortgezet onderwijs. Het is een onderdeel van de International Olympiad in Informatics (IOI). De eerste ronde van de NIO bestaat uit 4 onderdelen A, B, C en D: Opgaven en links: PDF met alle opgaven van de NIO 2018 Digitaal inleverplatform van de NIO Hoofdwebsite van de NIO De 100 inzendingen met de meeste punten (mits deze minimaal 200 punten hebben gehaald) worden uitgenodigd voor de tweede ronde van de NIO in maart 2018 op de Technische Universiteit Twente. Om mee te doen aan de olympiade, moeten je uitwerkingen digitaal zijn ingeleverd bij de NIO voor 17 januari 2018. (Ze worden direct automatisch nagekeken en je kunt zo vaak nieuwe/verbeterde versies insturen als je wilt). De beste deelnemers van de tweede ronde gaan door naar de derde ronde om te bepalen wie er namens Nederland mee mag doen aan de Internationale Informatica Olympiade 2018 in september in Japan. 1
Inhoud Wat is de Informatica Olympiade?... 1 De PO... 3 Puntenverdeling:... 3 Inleveren en beoordeling:... 3 Automatisch nakijken... 4 Tips en tricks bij de A opgaven... 7 Algemene tips... 7 Opdracht 1: Rechthoek (1 pnt)... 7 Opdracht 2: Caesarcode (1 pnt)... 8 Opdracht 3: Mieren (1,5 pnt)... 9 Opdracht 4: Bitcoin (1,5 pnt):... 10 Wt geeft punten voor efficiëntie... 10 Algemene strategie... 11 Code... 11 En nu nog eens maar dan efficiënt... 12 Opdracht 5: Blokken (1 pnt)... 12 Kubus representatie... 13 Maximale vulling... 13 Minimale invulling... 14 2
De PO Als PO voor Informatica gaan we de een deel van de opgaven van de eerste ronde van de NIO maken, namelijk die van categorie A en C. Je bent zelf vrij om ook daadwerkelijk mee te doen aan de Olympiade met je uitwerkingen (Met het maken van de PO ben je al een flink eind op weg, je kunt dan zelf de overige opgaven nog maken voor meer punten). Meer details over hoe je meedoet en welke voorwaarden er zijn, vind je op www.informaticaolympiade.nl. Wt helpt je natuurlijk graag als je officieel mee wilt doen. De olympiade schrijft niet voor in welke programmeertaal je de oplossingen maakt. De opgaven zijn zo gemaakt dat je in principe in elke taal een oplossing kunt schrijven. In dit document ga ik er van uit dat je het in Python doet. Als je liever een andere taal gebruikt, mag dat natuurlijk ook. Puntenverdeling: Je doet deze PO alleen of met zijn tweeën. ( Let op: De deelname aan de NIO zelf is individueel, dus van een duo kan maar 1 persoon deelnemen, omdat je niet 2x dezelfde code in kunt/mag leveren. Als je toch allebei wilt deelnemen, zul je verschillende versies van je oplossingen moeten maken.) We maken voor de PO alleen opgaven A en C (en eventueel D als je wilt). Opgaven B worden uniek gegenereerd voor elke deelnemer. Deze opagevn zeker ook maken als je aan de Olympiade mee wilt doen, maar voor de PO slaan we ze over, omdat deze voor Wt niet na te kijken zijn. De beoordeling van de PO is als volgt: - 1 punt cadeau - Je verslag met toelichting op je code en logboek leveren samen 1 punt op - Met het maken van opgaven A1 t/m A4 kun je 5 punten verdienen: o A1: 1,0 punt o A2: 1,0 punt o A3: 1,5 punt o A4: 1,5 punt - Met het maken van A5, en opgave C (en eventueel D als superbonus), zijn de laatste 3 punten te verdienen: o A5: 1,0 punt o C1: 1 punt o C2: 1 punt Deels correcte oplossingen van een opgave leveren een deel van de punten op, dus ook al kom je er niet uit, lever je beste poging van elke opgave toch in. Let op: de opgaven lopen op in moeilijkheid, maar de puntenwaardering ligt relatief zwaar op de eerste opgaven. Zo moet het voor iedereen mogelijk zijn een nette voldoende te halen en voor de doorzetters zijn hogere punten haalbaar. Inleveren en beoordeling: Je levert bij Wt je gemaakte programma s in. Daarnaast maak je een begeleidend verslag waarin je de code uitlegt. Hiermee help je mij jouw/jullie code te begrijpen en laat je zien dat je begrijpt wat je gedaan hebt (plagiaatcontrole). Tevens lever je een logboekje in met de bestede tijd per persoon. Dit 3
geeft mij inzicht in jullie tijdsbesteding en kan dienen als bewijsmateriaal bij eventuele onenigheid binnen een duo. Let op: Omdat deze PO plagiaat gevoelig is (Wt heeft er eerder problemen mee gehad), let ik extra op het kopiëren van andermans code en op de codetoelichting die je in je verslag geeft. Bij twijfel zal Wt om een mondelinge toelichting vragen om te checken of je je eigen code hebt geschreven en snapt wat je hebt gedaan! Elkaar een beetje helpen mag natuurlijk (en wordt zelfs aangemoedigd), maar code inleveren die je zelf niet helemaal begrijpt en dus niet kan toelichten, mag niet. Je levert alle onderdelen van de PO (de programma s en het verslag) in in een.zip file bij de opdracht die openstaat in Magister. Je hoeft per groepje uiteraard maar 1 keer in te leveren. Uiterlijke inleverdatum: Zondag 3 december om 23:59 uur Automatisch nakijken De informatica olympiade werkt met een automatisch nakijksysteem. Hierbij kun je een script dat je geschreven hebt als oplossing voor een van de opgaven inleveren. Er worden dan een aantal testcases automatisch op losgelaten en je krijgt punten voor elke testcase waarvoor je script het juiste antwoord geeft. Voor de testcases die misgaan krijg je te zien wat de foutmelding was (bv. Een fout antwoord, een error bij het runnen, maximale runtime overschreden, etc.). Met deze info kun je je script eventueel verbeteren en weer een nieuwe versie indienen. Je kunt elke opgave zo vaak proberen als je wilt, de hoogste score blijft staan. Zelfs als je niet aan de officiële olympiade wilt meedoen kun je hier dankbaar gebruik van maken om je PO opgaven te testen. Het werkt als volgt: Ga naar http://submit.informaticaolympiade.nl/ en maak een account aan. Als je bent ingelogd zie je links alle A, B en C opgaven staan (en 00 is een testopgave voor als je wilt checken of het inleveren werkt). Klik op de opgave je je wilt inleveren en klik Voeg inzending toe. Je kunt nu een script uploaden. Vergeet niet de juiste programmeertaal te selecteren (Python 2): 4
Als je op opslaan klikt wordt je script geupload en uitgevoerd. Je ziet dan je inzending in de lijst staan met de status uitvoeren. Dit betekent dat je script op dit moment uitgeveord wordt. Als je na een paar seconden de pagina ververst (F5), dan zie je als het goed is de status op klaar staan en is er een score gegeven (max 40 voor elke A opgave): Je kunt nu op de inzending klikken voor meer info en eventuele foutmeldingen als je niet alle 40 punten hebt gehaald: 5
Als er foutmeldingen staan in de rechtertabel kunnen die je helpen de fouten in je script op te sporen. De rest van dit document bevat tips, hints en uitleg om je te helpen de A opgaven te tackelen. 6
Tips en tricks bij de A opgaven Algemene tips - Bij elk van deze opgaven wordt er 1 of meer keer om input gevraagd (bij het automatisch nakijken wordt je programma getest met verschillende inputs). Gebruik hiervoor steeds simpelweg raw_input() De Olympiadesoftware gaat er vanuit dat je precies zoveel input leest als in de opgave is opgegeven en dat je precies het antwoord geeft zoals is voorgeschreven. Je moet dus geen extra tekst of tussenuitkomsten afdrukken. Doe dus ook niet zoiets als raw_input("geef een getal"), maar simpelweg raw_input() (met niets tussen de haakjes), zodat er niets extras op het scherm wordt geprint. Je opgave wordt anders niet goedgerekend door de nakijksoftware. - Om je programma s te testen is het vervelend steeds de invoer te moeten intypen. Zet tijdens het testen de waardes waar je mee wilt testen rechtstreeks in de code (en commentaar de input even weg). Dat scheelt je een hoop typewerk. Niet vergeten de input weer terug te zetten voordat je de opgave instuurt naar de nakijksoftware! Opdracht 1: Rechthoek (1 pnt) Deze eerste opgave is een echte opwarmer en zou je met slechts enkele regels Python moeten kunnen maken. Een paar tips: - De invoer bevat 2 waardes op 1 regel. Deze moet je elk apart in een variabele zien te krijgen. Je kunt hiervoor de ingelezen string slicen. Dit levert wel wat gedoe op omdat je tevoren niet weet hoe lang de invoer is en dus op welke positie de tweede waarde begint. - Een betere truuk is om de.split() methode te gebruiken. Die splitst een string in een lijst met substrings. Je kunt daarna de individuele substrings uit die lijst halen. Tussen de haakjes van split zet je het teken waarop je wilt splitsen. Een voorbeeld met splitten op komma: boodschappen = "appels,peren,druiven" boodschappenlijst = boodschappen.split(",") #split op komma s print boodschappen #originele string print boodschappenlijst #gesplitse lijst print boodschappenlijst[2] #3 e item uit de gesplitste lijst Uitvoer: "appels,peren,druiven" ["appels", "peren", "druiven"] druiven" - Gebruik een for loop om het juiste aantal rijen van de kubus te printen 7
- Vergeet niet dat je in Python strings kunt vermenigvuldigen. Dus "x" * 6 geeft "xxxxxx". Dat komt hier bijzonder goed van pas om het juiste aantal sterren op elke rij af te drukken Opdracht 2: Caesarcode (1 pnt) In deze opgave moet je letters gaan opschuiven. Om dat makkelijker te kunnen doen, moet je de letters gaan interpreteren als cijfers. Dan kun je er namelijk een getal bij optellen (het cijfer naar boven opschuiven als het ware) en dan weer terug omzetten naar de bijbehorende letter. Er is gelukkig al een logische omzetting van letters (tekens) naar cijfers, namelijk de ASCII tabel (klas 4, weet je nog??). Hier is een overzicht van de 1 e 128 tekens van de ASCII tabel en hun bijbehorende getalwaarde (de 1 e 32 zijn grotendeels inmiddels ongebruikte commando s uit de tijd van de telegraaf, de bruikbare tekens beginnen bij 32): In deze opgave wordt alleen met de hoofdletters gewerkt. De hoofdletters zitten netjes naast elkaar in de ASCII tabel met waarde 65 (A) t/m 90 (Z). In python kun je een letter omzetten naar de bijbehoordende ASCII waarde met ord(): code = ord("q") print code 8
Uitvoer: 81 Andersom kan ook natuurlijk. Het ASCII teken opvragen van een bepaalde cijferwaarde gaat als volgt: letter = chr(87) print letter Uitvoer: "W" Nu je weet hoe je letters kunt omzetten naar cijfers en terug, wordt het verschuiven van de letters volgens de Caesarcode een redelijk eenvoudig rekensommetje: - Lees de twee inputs (het woord en de verschuivingswaarde) in - Doorloop met een loop alle letters uit het woord. Voor elke letter: o Zet de letter om naar de ASCII getalwaarde o Tel er de verschuiving bij op o Verzin iets slims om te voorkomen dat je voorbij de Z gaat en weer vooraan bij de A begint o Zet het resulterende getal weer terug om naar de letter die er bij hoort - Print het resultaat Opdracht 3: Mieren (1,5 pnt) Voordat je je programma gaat maken, is het goed je het volgende te realiseren: Het verplaatsen van de mieren gaat in ronden (in het voorbeeld zijn dat 3 ronden). Elke ronde verplaatsen alle mieren die die dat kunnen zich tegelijkertijd (kijk maar eens goed naar het voorbeeld). Als je de mieren 1 voor 1 zou verplaatsen terwijl je door de lijst loopt, heb je het risico dat je een mier 2 of meer keer per ronde verplaatst. Om dit te regelen verdelen we een ronde in 2 stappen: - Doorloop de lijst met mieren en sla op op welke posities er mieren zijn die zich nu kunnen verplaatsen (je wisselt steeds 2 mieren, dus het opslaan van de locatie van de linker-mier van zo n paar is voldoende, de ander zit er per definitie rechts naast - Als alle plaatsen waar een mierwissel moet komen bekend zijn, wisselen we alle mierparen op deze plekken Als voorbeeld de situatie na stap 1 in het voorbeeld: Bepaal de plekken waar een mier staat die met zijn rechterbuurman moet wisselen (plek 1 en 3): Wissel nu alle mieren op die plekken met het rechterbuurman: 9
Nu wat tips over de opbouw van je code: - Het inlezen van de input spreekt redelijk voor zich inmiddels. Als je slim bent, gebruik je weer de.split() methode van Python om de dubbele invoer in te lezen. - Nu je de linkse (rode) en rechtse (blauwe) mieren apart hebt ingeladen, is het handig een variabele te maken met daarin de hele mierenopstelling. Plak dus de linkse en rechtse mierenrij aan elkaar in een nieuwe variabele. Let op: de linkse (rode) moet in spiegelbeeld t.o.v. de invoer. (Een prima excuus om even te kijken hoe je dat ook alweer slim kunt doen in Python)) - De mierenrij die je hierboven hebt gemaakt is een string (de ingevoerde blauwe en rode rij waren immers ook strings). Omdat we zometeen mieren willen wisselen, is het handig om de string om te zetten naar een list. Voorbeeldje: tekst = "appeltaart" lijst = list(tekst) print lijst uitvoer: ['a', 'p', 'p', 'e', 'l', 't', 'a', 'a', 'r', 't'] - We hebben nu een list die bijhoudt welke mier op welke plek staat, maar we moeten van elke mier ook bijhouden of deze bij de linkse of rechtse groep mieren hoort (of hij rood of blauw is). Het ligt voor de hand een tweede list te maken, die precies even lang is als onze mierenlijst, maar die simpelweg bestaat uit de letters L en R, om aan te geven bij welke groep de mier op die plek hoort. We noemen dit maar even de soortlijst Voor het voorbeeld zou dit zijn: ['L', 'L', 'L', 'R', 'R', 'R', 'R'] Bij de start zijn immers de groepen nog niet gemengd en in het voorbeeld zijn er 3 rode en 4 blauwe mieren - Nu je alle administratie op orde hebt kunnen we de wisselstappen gaan uitvoeren. Maak een for loop die precies evenveel keer wordt uitgevoerd als aangegeven in de invoer (in het voorbeeld dus 3). Daarbinnen doe je het volgende: o Maak een for-loop die langs alle mieren loopt en voor elke mier van het type L checkt of zijn rechterbuurmen er een van het type R is. Zo ja sla deze positie op o Maak daarna een tweede for-loop die langs alle opgeslagen posities loopt en de mier op die plek in de mierenlijst wisselt met zijn rechterbuurman. (Let op dat je niet alleen de mierenlijst wisselt, maar ook de soortlijst!) - Print de uiteindelijke mierenlijst op het scherm als je eindantwoord. Opdracht 4: Bitcoin (1,5 pnt): Wt geeft punten voor efficiëntie In deze opgave wordt je beloond voor efficiëntie (of bestraft voor inefficiëntie zo je wil). Als je programma de 1 e 9 testcases van de nakijksoftware goed doorstaat, krijg je 1 punt voor deze opgave. Als je ook het 10 e testgeval doorstaat, krijg je 1,5 punt voor deze opgave. Het 10 e testgeval is er een 10
met een hele grote invoer (100000 waarden) en als je niet efficiënt programmeert, is je programma niet klaar binnen de voorgeschreven 2 seconden en wordt je oplossing fout gerekend. Met de uitleg hieronder maak je een Pythonscript dat de opgave prima oplost, maar niet super efficiënt is qua uitvoeringstijd. Op het einde van de uitleg zal ik je tips geven hoe je efficiënter te werk kunt gaan en het 10 e geval ook kunt halen in 2 seconden. Achtergrondinfo: Ondanks dat computers steeds sneller worden, kan efficiënt programmeerwerk het verschil maken tussen een razendsnelle of trage berekening. (Het informatica sub-gebied dat hier over gaat heet computational-complexity. Als je dat interessant vindt is hier een goed artikel dat het heel aardig uitlegt/illustreert) Algemene strategie Voordat we naar de code gaan, eerst wat algemene observaties. In deze opgave moet je, gegeven een lijst met bitcoinwaardes, bepalen hoe je daar de meeste winst uit kunt slepen met het steeds kopen en verkopen van een enkele bitcoin. Wat voor strategie is er nodig om dat te bereiken? Logischerwijs moet je steeds de bitcoins zo laag mogelijk inkopen en zo hoog mogelijk verkopen. Wat hier eigenlijk gevraagd wordt is dat je op zoek gaat naar (lokale) minima en maxima in een grafiek, want dat zijn de meest winstgevende plekken om te kopen en te verkopen. Zie de volgende schetsen: Onze strategie wordt dus: - Doorloop de lijst met waardes en sla op waar zich lokale minima en maxima bevinden - Koop bitcoin op elk minimum en verkoop weer als er een maximum voorbij komt - Heb je op het einde nog bitcoin? Verkoop deze ook Code Nu dit nog even in code omtoveren : - Om de invoer in te lezen kun je het volgende doen: o Lees het eerste getal in (Dat geeft aan hoeveel waardes er volgen) o Maak een for-loop met precies zoveel stappen als dat eerste getal aangeeft. Elke stap van de for-loop lees je een getal in en sla je deze op in een list 11
o Je eindigt nu met een list met alle waardes erin. O.b.v. het voorbeeld: [5, 11, 4, 2, 8, 10, 7, 4, 3, 6] - Maak nu 2 extra lists: minima[] en maxima[]. Doorloop de lijst met bitcoinwaardes. Als je een lokaal minimum of maximum tegenkomt, sla je de positie van dit minimum of maximum in de juiste lijst op. Een paar dingen om rekening mee te houden: o Bedenk goed hoe je kunt zien of iets een lokaal minumum of maximum is. o Wat doe je met de eerste en de laatste waarde uit de bitcoinlist (Moet je meteen aan het begin kopen? o Wat doe je als de waarde een paar dagen achter elkaar hetzelfde is? - Nu je weet waar de lokale minima en maxima zitten, kun je simpelweg door de dagen heenlopen in je bitcoinlijst. Kom je een minimum tegen (zoek steeds op in je minima lijst), dan koop je bitcoin voor die prijs. Als je een maximum tegenkomt, verkooop je bitcoin voor die prijs. Zo kom je aan je uiteindelijke antwoord. En nu nog eens maar dan efficiënt De hoofdreden dat bovenstaande uitwerking de 10 e testcase niet haalt binnen 2 seconden, is dat we eerst alle invoer inlezen en in een lijst stoppen. Dan lopen we nog een keer door de hele lijst, op zoek naar minima en maxima in deze lijst. Daarna lopen we zelfs nog een keer door de lijst om de aankopen en verkopen te doen. Dit kost (onnodig) veel tijd (en veel geheugenruimte voor alle waarden in de lijst!) en dat merk je bij grote lijsten zoals die van testcase 10. Wat kunnen we doen om het te verbeteren? Doorloop de lijst 1 keer i.p.v. 3 keer en doe alle berekeningen ter plekke! In de vorige uitwerking lazen we eerst alles in en gingen daarna pas naar de invoer kijken en er wat mee doen. Je kunt ook prima minima en maxima bepalen tijdens het inlezen. Zodra je een minimum tegenkomt, koop je meteen je bitcoin en bij een maximum verkoop je deze meteen weer. Een paar dingen om rekening mee te houden: - Lees het eerste getal zodat je weet hoeveel er gaan komen - Om een minimum of maximum te detecteren, moet je weten welke waarden er voor en er na komen. Nu je ter plekke alles inleest, moet je dus eigenlijk 1 plek vooruit lezen om te bepalen of de huidige waarde een maximum of minimum is. - Let hierbij goed op dat je niet buiten de lijst gaat lezen als je op het einde van de lijst aankomt. Meer hulp nodig? Vraag Wt om meer tips.. Opdracht 5: Blokken (1 pnt) Deze opdracht is lastig (Wt kreeg er hoofdpijn van, tot er eindelijk een Eureka-moment kwam ), omdat het niet vanzelfsprekend is hoe je aan je antwoord moet komen. Als je eenmaal hebt uitgedokterd hoe je aan de maximale en minimale (oef die doet pijn) vulling moet komen, is het programmeerwerk niet eens zo lastig. Hier een paar hints om je op weg te helpen. 12
Kubus representatie Je doet in deze opgaven berekeningen over een 3-dimensionaal object (de vierkante tafel (2D) met de bijbehorende stapelhoogte (3 e dimensie)). Voordat je aan het rekenen/uitproberen kunt gaan, zul je eerst een manier moeten verzinnen om de kubus in je programma/geheugen op te slaan. Een eerste logische gedachte zou zijn om een 3D list te maken (dus een list met lists met daarin lists). Dat is zeker mogelijk, maar het werkt niet fijn. Je moet dan een loop in een loop in een loop zetten om door de plekken in de kubus heen te lopen en dat levert een boel hoofdpijn op. Slimmer is om een 2D list te maken (een list met daarin lists) om een matrix te krijgen gelijk aan het grondvlak van de tafel. De waarden van elke vakje uit het grondvlak is simpelweg de hoogte van de stapel op die plek. Je krijgt zo eigenlijk een soort bovenaanzicht van de tafel. De maximale vulling van het voorbeeld ziet er dan zo uit: Dit is overzichtelijk en heeft nog wel alle informatie van het 3D object, maar dan in 2D vorm. Een voorbeeld bij het maken van 2D lists: https://stackoverflow.com/questions/6667201/how-todefine-two-dimensional-array-in-python#answer-6667288 Maak in eerste instantie een 2D list van de goede afmetingen (variabele B uit de opgave) en vul deze met 0-en (alle stapels zijn aan het begin leeg). Met slim denk/rekenwerk, moet je nu uitpluizen hoe je elke stapel tot de maximale hoogte aanvult om de maximale kubus te krijgen Voor de minimale kubus maak je weer een leeg 2D array (gevuld met 0-en) en probeert alleen die stapels neer te zetten die absoluut nodig zijn. Vervolgens tel je het aantal blokjes in beide kubussen voor het goede eindantwoord. Maximale vulling De maximale vulling is relatief eenvoudig. Kijk goed naar het voorbeeld en bedenk hoe je aan de beide aanzichten kunt zien hoe hoog elke stapel maximaal kan zijn. 13
Minimale invulling Dit is het lastige deel van deze opgaven, want er zijn veel verschillende soorten aanzichten te verzinnen waarbij bepaalde oplossingen toch niet kloppen. Probeer zelf verschillende testgevallen te verzinnen (op papier of in excel bijvoorbeeld) om je eigen ideeën goed te testen. Loop je echt vast, kan Wt nog even met je meedenken. Succes met C1 en C2! 14