NIO Runde 2

Like dokumenter
NIO Runde / Oppgaveløsninger

Pizzatur. Input. Output. Begrensninger. Norsk Informatikk Olympiade Runde /2019. Oppgavenr.: 1

Norsk informatikkolympiade runde

Norsk informatikkolympiade runde

HØGSKOLEN I SØR-TRØNDELAG Avdeling for informatikk og e-læring - AITeL

HØGSKOLEN I SØR-TRØNDELAG

Grunnleggende Grafteori

Backtracking som løsningsmetode

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2017

Oppgave 1. Sekvenser (20%)

Løsnings forslag i java In115, Våren 1996

HØGSKOLEN I SØR-TRØNDELAG

UNIVERSITETET I OSLO

ALGORITMER OG DATASTRUKTURER

GRAFER. Korteste vei i en vektet graf uten negative kanter. Korteste vei, en-til-alle, for: Minimale spenntrær

UNIVERSITETET I OSLO

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2015

INF1020 Algoritmer og datastrukturer GRAFER

Norsk informatikkolympiade runde

Norsk informatikkolympiade runde

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Høgskolen i Gjøvik. Avdeling for elektro- og allmennfag E K S A M E N. EKSAMENSDATO: 12. desember 1995 TID:

TDT Prosedyre- og objektorientert programmering

Mattespill Nybegynner Python PDF

Programmering i C++ Løsningsforslag Eksamen høsten 2005

EKSAMEN. Dato: 9. mai 2016 Eksamenstid: 09:00 13:00

HØGSKOLEN I SØR-TRØNDELAG

Løsningsforslag for eksamen i fag TDT4120 Algoritmer og datastrukturer Tirsdag 9. desember 2003, kl

Grafalgoritmer: Korteste vei

1 ØVING I WINDOWS FRA CHRISTIAN ANDOLO

INF1010 notat: Binærsøking og quicksort

HØGSKOLEN I SØR-TRØNDELAG Avdeling for informatikk og e-læring - AITeL

Backtracking som løsningsmetode

INF Algoritmer og datastrukturer

Dagens plan: INF Algoritmer og datastrukturer. Eksempel. Binære Relasjoner

Ny/utsatt EKSAMEN. Dato: 6. januar 2017 Eksamenstid: 09:00 13:00

HØYSKOLEN I OSLO, AVDELING FOR INGENIØRUTDANNING

Løsnings forslag i java In115, Våren 1998

HØGSKOLEN I SØR-TRØNDELAG Avdeling for informatikk og e-læring - AITeL

Disjunkte mengder ADT

Informasjon Eksamen i IN1000 høsten 2017

Finne ut om en løsning er helt riktig og korrigere ved behov

Longest increasing. subsequence Betingelser. Longest. common subsequence. Knapsack Grådig vs. DP Moro: 2D-Nim Spørsmål. Forside. Repetisjon.

E K S A M E N. Algoritmiske metoder I. EKSAMENSDATO: 11. desember HINDA / 99HINDB / 99HINEA / 00HDESY ( 2DA / 2DB / 2EA / DESY )

Oppgave: FIL File Paths

INF Algoritmer og datastrukturer

Algoritmer og datastrukturer Kapittel 11 - Delkapittel 11.2

EKSAMEN. Emne: Algoritmer og datastrukturer

Løkker og arrayer. Løse problemer med programmering. INF1000, uke3 Geir Kjetil Sandve

SIF8010 ALGORITMER OG DATASTRUKTURER

Binære trær: Noen algoritmer og anvendelser

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2014

GRAFER. Noen grafdefinisjoner. Korteste vei i en uvektet graf V 2 V 1 V 5 V 3 V 4 V 6

EKSAMEN med løsningsforslag

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2013

INF1010 Sortering. Marit Nybakken 1. mars 2004

Kompleksitet. IN algoritmer og datastrukturer Plenumstime / repetisjon

INF Algoritmer og datastrukturer

IN1010 våren januar. Objektorientering i Java

Longest. increasing. subsequence. Betingelser. Matrise- common. Grådig vs. DP. Forside. Intro. Fibonacci-tall. Memoisering DP

Norsk informatikkolympiade runde

INF1010 våren januar. Objektorientering i Java

Del 3: Evaluere uttrykk

K O N T I N U A S J O N S E K S A M E N

Eksamensoppgave i TDT4120 Algoritmer og datastrukturer

Norsk informatikkolympiade runde

BOKMÅL Side 1 av 5. KONTERINGSEKSAMEN I FAG TDT4102 Prosedyre og objektorientert programmering. Onsdag 6. august 2008 Kl

OPPGAVE 5b og 8b Java Kode

Hva er en kø? En lineær datastruktur der vi til enhver tid kun har tilgang til elementet som ble lagt inn først

EKSAMEN. Dato: 28. mai 2018 Eksamenstid: 09:00 13:00

UNIVERSITETET I OSLO

Hva er verdien til variabelen j etter at følgende kode er utført? int i, j; i = 5; j = 10; while ( i < j ) { i = i + 2; j = j - 1; }

Uke 5 Disjunkte mengder

Rekursjon som programmeringsteknikk

INF1020 Algoritmer og datastrukturer GRAFER

EKSAMENSOPPGAVE. INF-1100 Innføring i programmering og datamaskiners virkemåte. Ingen. Elektronisk (WiseFlow) Robert Pettersen

Løsningsforslag for Obligatorisk Oppgave 2. Algoritmer og Datastrukturer ITF20006

INF Algoritmer og datastrukturer

Oppgavesettet består av 7 sider, inkludert denne forsiden. Kontroll& at oppgaven er komplett før du begynner å besvare spørsmålene.

Dagens plan. INF Algoritmer og datastrukturer. Koding av tegn. Huffman-koding

Norsk informatikkolympiade runde

Norsk informatikkolympiade runde. Sponset av. Uke 46, 2016

Vi skal se på grafalgoritmer for:

"behrozm" Oppsummering - programskisse for traversering av en graf (dybde først) Forelesning i INF februar 2009

Plan: Parameter-overføring Alias Typer (Ghezzi&Jazayeri kap.3 frem til 3.3.1) IN 211 Programmeringsspråk

Løsningsforslag til INF110 h2001

HØGSKOLEN I SØR-TRØNDELAG

Korteste vei i en vektet graf uten negative kanter

Hva er en kø? En lineær datastruktur der vi til enhver tid kun har tilgang til elementet som ble lagt inn først

IN2010: Forelesning 11. Kombinatorisk søking Beregnbarhet og kompleksitet

Algoritmer og Datastrukturer

UNIVERSITETET I OSLO

UNIVERSITETET I OSLO

Anvendelser av grafer

Minimum Spenntrær - Kruskal & Prim

E K S A M E N. Algoritmiske metoder I. EKSAMENSDATO: 11. desember HINDA / 00HINDB / 00HINEA ( 2DA / 2DB / 2EA ) TID:

EKSAMEN Løsningsforslag. med forbehold om bugs :-)

Generelle Tips. INF Algoritmer og datastrukturer. Åpen og Lukket Hashing. Hashfunksjoner. Du blir bedømt etter hva du viser at du kan

Array&ArrayList Lagring Liste Klasseparametre Arrayliste Testing Lenkelister

EKSAMEN. Algoritmer og datastrukturer

Transkript:

NIO 2018-2019 Runde 2 1 Pizzatur Her skulle man finne ut av hvor lang tid man bruker på å besøke en rekke punkter. Siden rekkefølgen punktene skulle besøkes i var oppgitt trengte ikke programmet å gjøre noen avgjørelser av hva som er optimalt - en enkel simulering av turen fungerte fint. For hver restaurant så måtte man legge til 15 minutter til en løpende total, og dersom det ikke var det første punktet så måtte man også legge til Manhattan-avstanden til den forrige restauranten. (Manhattan-avstanden mellom to punkter er hvor langt man må gå for å komme mellom de dersom man kun kan bevege seg direkte vertikalt og horisontalt og kan beregnes ved at Manhattan-avstanden mellom (X 1, Y 1 ) og (X 2, Y 2 ) er X 1 X 2 + Y 1 Y 2 ) Dersom man fikk feil svar på den siste deloppgaven så var det sanynsligvis fordi man brukte 32-bits tall for å holde på svaret. Når X i og Y i kunne være opp til 10 6 og det kunne være 10 5 punkter så ser vi med litt overslagsregning at den totale reiseavstanden kan være opp til nesten 2 10 1 1. Et 32-bits tall med fortegn (int i C++ eller Java) kan kun inneholde tall opp til ca. 2 10 9. Man må derfor bruke 64-bits tall (long long i C++ eller long i Java). 2 Datamaskiner Her hadde man en masse teknologier med avhengigheter i mellom seg, og man måtte finne ut av hvor mange man måtte utvikle for å finne ut av en spesifik en. Man ser at teknologiene og teknologi-avhengighetene definerer det som kalles en rettet graf hvor teknologiene er noder og avhengighetene er kanter. Grafen er rettet (dvs. at alle kantenen har en retning) fordi avhengighetene bare går én vei - man trenger matematikk for å utvikle katapulter men man trenger ikke katapulter for å utvikle matematikk. Mer spesifikt så sier begrensningen om at alle teknologiene vil la seg utforske at grafen er en asyklisk rettet graf fordi ingen teknologier kan avhenge av noe som krever at teknologien allerede er utviklet. Konseptuelt virker det veldig greit å lage en algoritme for å finne ut av svaret: Prøv å utvikle datamaskiner Dersom du på noe tidspunkt skal utvikle en teknologi som har avhengigheter, utvikl de avhengighetene først. 1

Tell opp hvor mange teknologier som har blitt utviklet og skriv ut det tallet Det er derimot ikke helt trivielt å få dette til å kjøre raskt nok til å ta alle deloppgavene. En første tilnerming vil kanskje være følgende C++ kode map<string, vector<string> > avhengigheter; set<string> utviklet; void utvikl(string teknologi) { for (int i = 0; i < avhengigheter[teknologi].size(); i++) utvikl(avhengigheter[teknologi][i]); utviklet.insert(teknologi);... Initier avhengigheter og utviklet fra input... utvikl("datamaskiner"); cout << utviklet.size() << endl; Men et problem her er at man kan komme til å kalle utvikl flere ganger for samme teknologi, og dermed behandle hver avhengighet flere ganger, noe som kan resultere til et eksponensielt antall kall til utvikl. Dersom man legger inn en sjekk for dette i utvikl metoden så kan man ungå at den blir kalt flere enn maksimalt M ganger. void utvikl(string teknologi) { if (utviklet.find(teknologi)!= utviklet.end()) return; for (int i = 0; i < avhengigheter[teknologi].size(); i++) utvikl(avhengigheter[teknologi][i]); utviklet.insert(teknologi); 3 Suluklak Her skulle man behandle en graf som til å begynne med bestod av N noder og ingen kanter. Deretter fikk man en rekke kanter som man skulle avvise dersom de ville gjøre at node 0 og 1 blir forbundet, eller legge til i grafen dersom de ikke gjør det. For den første kanten er det veldig enkelt å svare på spørsmålet, men det blir vanskeligere etterhvert som grafen oppdaterer seg. Den mest direkte løsningen er nok Legg den nye kanten til i grafen Gjør et bredde-først søk fra node 0 og se om du kan nå node 1 Hvis du kan - fjern kanten fra grafen og skriv ut nei 2

Hvis du ikke kan - behold kanten og skriv ut ja Men et bredde-først søk kan ta lang tid dersom grafen er stor. Denne algoritmen vil ikke kunne løse de største datasettene. En litt lurere algoritme vil være å se at hvilke kanter som til en hver tid er med i grafen er ikke så farlig. Det eneste som er av betydning er om noder befinner seg i samme sammenhengende komponent eller ikke. For å se om man skal avvise veien mellom a og b så kan man da sjekke om (komponenten med a er komponenten med 0 OG komponenten med b er komponenten med 1) ELLER (komponenten med a er komponenten med 1 OG komponenten med b er komponenten med 0). Med noen hjemmesnekra datastrukturer for å holde styr på komponentene kan man få kjøretiden ned til O(N 2 + M), men det finnes en veldig effektiv datastruktur for slike disjunkte sett som gir enda raskere kjøretid og som dermed kan løse den siste testsettgruppen. Hvert element har en overordnet som kan være seg elementet selv. Representanten til et element er elementet selv dersom den er sin egen overordnede - hvis ikke er den den samme som representanten til sin overordnede. To elementer tilhører samme sett hvis og bare hvis de har samme representant. Man kan dermed raskt slå sammen to sett vet å sette den overordnede til det ene settet sin representant til å være representanten for det andre settet. Ved å komprimere stiene som fører til representantene nå man spør om representantene så sørger man for at kjøretiden ikke blir lang dersom man gjør mange spørringer på en graf med veldig lange kjeder. vector<int> overordnet; //initiert til 0, 1, 2,... N-1 int representant(int e) { if (overordnet[e] == e) { return e; else { overornet[e] = representant(overordnet[e]); return overordnet[e]; void knyttsammen(int a, int b) { overordnet[representant(a)] = representant(b); bool skalbygge(int a, int b) { if (representant(a) == representant(0) && representant(b) == representant(1)) 3

return false; if (representant(a) == representant(1) && representant(b) == representant(0)) return false; return true; 4 Bare rør Dette er et typisk optimaliseringsproblem hvor det finnes mange måter å gjøre en ting på, men du skal klare å finne den beste. For små datasett så er det mulig å gjøre brute force forsøk. For å løse et nettverk kan du gå gjennom alle mulige par av naborør, prøve å koble de sammen, og se hva kostnaden blir av å koble sammen det restrerende nettverket. Dvs. noe slik som int optimal(vector<int> a) { if (a.size() == 1) return 0; //har vi ett rør er vi ferdige. int best; for (int i = 0; i < a.size() - 1; i++) { //kobl sammen rør i med rør i+1 vector<int> rest; for (int j=0;j<a.size();j++) { if (j < i j > i+1) rest.push_back(a[j]); if (j == i) rest.push_back(a[j] + a[j+1]); int kostnad = optimal(rest) + ((a[i]+a[i+1])*311)\%104729; if (i==0 kostnad < best) best = kostnad; return best; Dette blir nok alt for trengt for å løse noe annet enn den første subtasken. For å få til en løsning som gir full poengsum kan man begynne med å se på hva den siste sveisingen blir. Uansett hvor man gjøre den siste skjøten, så koster den det samme. Kostnaden av å bygge opp de to delene kan derimot være forskjellig avhengig av hvor skjøten en. Hvis den siste skjøten er mellom komponent X og X + 1 så ser vi at vi først må ha klart å sveise sammen alle komponentene fra 0 til og med X og alle komponentene fra X + 1 til N 1. Hvis vi lar F (A, B) være kostnaden av å sveis sammen alle komponentene fra og med A til og med B så ser vi dermed at F (A, B) = 0 dersom A = B F (A, B) =minimum over alle X med A X < B av F (A, X) + F (X + 1, B) + ([lengen av hele stykket] 311)%104729 ellers Programmerer man dette som en ren rekursjon så vil den fortsatt bruke alt for lang tid, men det er kun fordi den ender opp med å gjøre gjentatte kallene til F med de 4

samme verdiene for A og B. Hvis vi lagrer unna resultater vi har funnet ut av i en 2-dimensjonal array så slipper vi å beregne de om igjen. Vi ender dermed opp med en kjøretidskompleksitet på O(N 3 ) som kjører fint innenfor inputbegrensningene. 5 Fantasy Dette var enda et optimaliseringsproblem hvor man skulle klare å maksimere antall poeng man kunne få over et sett med runder. Den maksimale poengsummen det er mulig å få i en enkel runde er lett å finne ut av - bare summer opp de K største verdiene i den runden. Problemet er at valg av et lag i en runde setter begrensninger for hvilket lag man kan ha i neste runde. Man kan tenkte seg at alle de forskjellige lagene er noder i en graf, med kanter mellom lag som har maksimalt én spiller byttet ut mellom seg. For hver runde så får man poeng for den noden man er på, og kan deretter flytte seg til en nabonode. De tre første deloppgavene var veldig enkle her. I den første så hadde man ikke noe valg, og måtte dermed ha med alle spillerene. I den andre så var det bare én spiller som skulle utelates i hver runde, noe som betyr at du står fritt til å sette sammen hvilket som helst lag for hver runde og kan dermed alltid ta det beste laget. I den tredje så var det bare én runde, så her var det også bare å plukke ut det beste laget. For å løse de to siste deloppgavene så måtte man gjøre litt smartere ting for å behandle de forskjellige mulige lagene. En veldig effektiv måten å representere et lag på er med en bitmask, der bit nummer i telt fra høyre er satt dersom spiller i er med på laget. En bitmask som 001101 vil da bety at spiller 0, 2 og 3 er med på laget. For fjerde deloppgave så kan man se for seg alle mulige lag i første runde, og for hver av disse se på hvilke mulige lag man kan bruke i andre runde. int N, M; vector<vector<int> > scores; int antallbits(int a) { int s = 0; for (int i=0;i<n;i++) { if ((a & (1<<i)) s++; return s; int score(int runde, int spillermaske) { int sum = 0; for (int i = 0; i < N; i++) { if (spillermaske & (1<<i)) { sum += scores[runde][i]; 5

return sum;... les inn input... int best = 0; for (int m = 0; m < (1<<N); m++) { if (antallbits(m) == M) { //m har M bits satt, og er dermed et lovlig lag //å ha i første runde int score = score(0, m); int bestrunde2 = score(1, m); //prøv med samme lag i andre runde for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if ((m & (1<<i)) &&!(m & (1<<j))) { //spiller i var med på laget, og spiller j var //ikke med, så vi prøver å bytte de om bestrunde2 = max(bestrunde2, score(1, m - (1<<i) + (1<<j)); best = max(best, score + bestrunde2); cout << best << endl; For siste deloppgave må man bruke dynamisk programmering. La B[r][m] være høyeste poengsum det er mulig å ha i runde r dersom man har lag m i den runden. Denne verdien kan vi finne ut av ved å se på alle mulige naboer av lag m og se hvor mye poeng de fikk i runde r 1. Ved å jobbe seg opp fra runde 0 til R 1 kan man dermed finne ut hva den beste poengsummen det er mulig å ende opp med er. 6

6 Implementasjoner //Pizzatur #include <iostream> using namespace std; int n; cin>>n; long long d = 0; int x0,y0; for (int i=0;i<n;i++) { int x,y; cin>>x>>y; if (i!= 0) { d += abs(x-x0)+abs(y-y0); x0 = x; y0 = y; d += 15; cout << d << endl; 7

//Datamaskiner #include <iostream> #include <vector> #include <set> #include <map> #include <string> using namespace std; map<string, vector<string>> G; set<string> V; int dfs(const string &u) { if (V.find(u)!= V.end()) return 0; V.insert(u); int res = 1; for (auto &v : G[u]) res += dfs(v); return res; int n; cin >> n; for (int i = 0; i < n; i++) { string u; int a; cin >> u >> a; for (int j = 0; j < a; j++) { string v; cin >> v; G[u].push_back(v); cout << dfs("datamaskiner") << endl; 8

//Suluklak #include <iostream> #include <vector> #include <algorithm> using namespace std; constexpr int MAXN = 100000; int n, m, p[maxn], s[maxn]; int find(int x) { return x == p[x]? x : p[x] = find(p[x]); void unite(int a, int b) { a = find(a); b = find(b); if (s[a] > s[b]) { s[a] += s[b]; p[b] = a; else { s[b] += s[a]; p[a] = b; cin >> n >> m; for (int i = 0; i < n; i++) { p[i] = i; s[i] = 1; for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; if (((find(0)!= find(a)) (find(1)!= find(b))) && ((find(0)!= find(b)) (find(1)!= find(a)))) { 9

unite(a, b); cout << "ja" << endl; else { cout << "nei" << endl; 10

//Bare rør #include <iostream> #include <vector> #include <algorithm> using namespace std; constexpr int MAXN = 802; int n; long long L[MAXN], M[MAXN][MAXN]; bool V[MAXN][MAXN]; inline long long cost(int a, int b) { return ((L[b] - L[a-1]) * 311ll) \% 104729ll; long long dp(int a, int b) { if (a == b) return 0; if (V[a][b]) return M[a][b]; V[a][b] = true; long long res = dp(a, a) + dp(a+1, b); for (int i = a; i < b; i++) res = min(res, dp(a, i) + dp(i+1, b)); res += cost(a, b); return M[a][b] = res; cin >> n; for (int i = 1; i <= n; i++) { cin >> L[i]; L[i] += L[i-1]; cout << dp(1, n) << endl; 11

//Fantasy football #include <vector> #include <algorithm> #include <iostream> using namespace std; int bitsset(int m, int n) { int s = 0; for (int i=0;i<n;i++) { if (m&(1<<i)) s++; return s; int roundscore(vector<int> &scores, int team) { int s = 0; for (int i=0;i<scores.size();i++) { if (team & (1<<i)) s += scores[i]; return s; int n,k,r; cin>>n>>k>>r; vector<vector<int> > scores(r, vector<int>(n, 0)); vector<int> teams; for (int i=0;i<r;i++) for (int j=0;j<n;j++) cin >> scores[i][j]; vector<vector<int> > dp(r, vector<int>(1<<n)); for (int m=0;m<(1<<n);m++) { if (bitsset(m, n) == k) { teams.push_back(m); dp[0][m] = roundscore(scores[0], m); for (int rr=1;rr<r;rr++) { for (auto &m : teams) { int rp = roundscore(scores[rr], m); int bestfrom = dp[rr-1][m]; for (int i=0;i<n;i++) { for (int j=0;j<n;j++) { 12

if (((m & (1<<i))) &&!(m & (1 << j))) { bestfrom = max(bestfrom, dp[rr-1][m - (1<<i) + (1<<j)]); dp[rr][m] = rp + bestfrom; cout << *max_element(dp[r-1].begin(), dp[r-1].end()) << endl; 13