d388e8a817f12c1330efc90d20d3c385e14384bc
resources/2018_Territoriali_soluzioni.md
... | ... | @@ -1,12 +1,13 @@ |
1 | 1 | ## Soluzioni — Selezione Territoriali 2018 |
2 | 2 | Di seguito spiegheremo brevemente come risolvere i quattro task delle selezioni territoriali 2018. |
3 | 3 | |
4 | -### Festa canina (<code>party</code>) — <span style="color: #1dd01d;">molto facile</span></h3> |
|
4 | +### Festa canina (`party`) - molto facile |
|
5 | 5 | |
6 | 6 | Mettendo da parte i problemi di amicizia e inimicizia del famosissimo Mojito, il succo del problema è questo: ci viene dato un array di numeri interi e vogliamo ottenere un risultato (anch'esso un numero intero). Una volta letto e compreso il testo del problema, possiamo facilmente convincerci del fatto che la soluzione sia calcolabile come la **somma** di tutti i numeri **positivi** presenti nell'array fornito. Infatti, se un numero è negativo, vuol dire che Mojito non gradisce la presenza di quello specifico cane, quindi ci basta non invitarlo (ovvero: non includerlo nella somma). |
7 | 7 | |
8 | 8 | Una semplice implementazione in C++ è la seguente: |
9 | -```[cpp] |
|
9 | + |
|
10 | +```c++ |
|
10 | 11 | int main() { |
11 | 12 | int T; |
12 | 13 | cin >> T; |
... | ... | @@ -24,15 +25,15 @@ int main() { |
24 | 25 | } |
25 | 26 | ``` |
26 | 27 | |
27 | -### Antivirus (<code>antivirus</code>) — <span style="color: green;">facile</span> |
|
28 | +### Antivirus (`antivirus`) - facile |
|
28 | 29 | |
29 | 30 | Anche qui, una volta estratto il problema dalla "storiella", ciò che ci viene richiesto è esprimibile in termini primitivi (variabili e array). In particolare, ci vengono fornite delle variabili (numeri interi) e 4 stringhe (array di caratteri). Vogliamo calcolare 4 indici (numeri interi) che rappresentano il "punto di partenza" del virus in ciascuna stringa. |
30 | 31 | |
31 | -Una possibile soluzione è scrivere 5 cicli for nidificati (ovvero: uno dentro l'altro, come una [matrioska](https://it.wikipedia.org/wiki/Matrioska). I 4 cicli più esterni faranno riferimento all'indice delle 4 stringhe (non è importante l'ordine). Per esempio, possiamo chiamare gli indici <code>i</code>, <code>j</code>, <code>k</code>, <code>l</code>. Il quinto ciclo, quello più interno, servirà invece a verificare che il virus sia effettivamente presente nelle posizioni indicate dai quattro indici. Per esempio, potremmo chiamare <code>m</code> l'indice di questo ciclo.</p> |
|
32 | +Una possibile soluzione è scrivere 5 cicli for nidificati (ovvero: uno dentro l'altro, come una [matrioska](https://it.wikipedia.org/wiki/Matrioska). I 4 cicli più esterni faranno riferimento all'indice delle 4 stringhe (non è importante l'ordine). Per esempio, possiamo chiamare gli indici `i`, `j`, `k`, `l`. Il quinto ciclo, quello più interno, servirà invece a verificare che il virus sia effettivamente presente nelle posizioni indicate dai quattro indici. Per esempio, potremmo chiamare `m` l'indice di questo ciclo. |
|
32 | 33 | |
33 | -Il codice che segue implementa la soluzione appena descritta (con un <code>+ M</code> nella condizione dei cicli for, per evitare che i vari indici "fuoriescano" dalle stringhe). |
|
34 | +Il codice che segue implementa la soluzione appena descritta (con un `+ M` nella condizione dei cicli for, per evitare che i vari indici "fuoriescano" dalle stringhe). |
|
34 | 35 | |
35 | -```[cpp] |
|
36 | +```c++ |
|
36 | 37 | void solve(int t) { |
37 | 38 | int N1, N2, N3, N4; |
38 | 39 | cin >> N1 >> N2 >> N3 >> N4; |
... | ... | @@ -70,23 +71,23 @@ int main() { |
70 | 71 | } |
71 | 72 | ``` |
72 | 73 | |
73 | -**ATTENZIONE!** Questa soluzione, sebbene sia sufficiente a prendere tutti i punti, non è quella asintoticamente ottimale. Infatti, la (complessità computazionale)[https://it.wikipedia.org/wiki/Teoria_della_complessit%C3%A0_computazionale] di questa soluzione, quando la esprimiamo come funzione della lunghezza massima <code>N</code> delle quattro stringhe e della lunghezza massima <code>M</code> del virus, diventa pari a: $O(N^4M)$. |
|
74 | +**ATTENZIONE!** Questa soluzione, sebbene sia sufficiente a prendere tutti i punti, non è quella asintoticamente ottimale. Infatti, la [complessità computazionale](https://it.wikipedia.org/wiki/Teoria_della_complessit%C3%A0_computazionale) di questa soluzione, quando la esprimiamo come funzione della lunghezza massima `N` delle quattro stringhe e della lunghezza massima `M` del virus, diventa pari a: $O(N^4M)$. |
|
74 | 75 | |
75 | -Dato che i valori massimi di <code>N</code> e <code>M</code> sono piuttosto bassi, il nostro programma terminerà la sua esecuzione in un tempo ragionevole. Tuttavia, se questo stesso problema fosse stato proposto alla *finale nazionale*, i valori sarebbero sicuramente stati molto più elevati. Esistono infatti soluzioni asintoticamente molto più efficienti di quella appena descritta! |
|
76 | +Dato che i valori massimi di `N` e `M` sono piuttosto bassi, il nostro programma terminerà la sua esecuzione in un tempo ragionevole. Tuttavia, se questo stesso problema fosse stato proposto alla *finale nazionale*, i valori sarebbero sicuramente stati molto più elevati. Esistono infatti soluzioni asintoticamente molto più efficienti di quella appena descritta! |
|
76 | 77 | |
77 | -### Radioanalisi fossile (<code>xray</code>) — <span style="color: blue;">medio</span> |
|
78 | +### Radioanalisi fossile (`xray`) - medio |
|
78 | 79 | |
79 | 80 | Un modo per risolvere questo problema è guardando ai "picchi". Possiamo convincerci che un algoritmo ottimale, in termini di numero di azionamenti della macchina, è il seguente: |
80 | 81 | |
81 | -- cerchiamo il massimo nell'array, ad esempio otterremo <code>8</code> se l'array fosse <code>5 8 6</code>; |
|
82 | -- lo riduciamo fino a che non diventa uguale al più grande dei suoi vicini, nel nostro caso <code>8</code> si riduce a <code>6</code> mediante 2 azionamenti; |
|
82 | +- cerchiamo il massimo nell'array, ad esempio otterremo `8` se l'array fosse `5 8 6`; |
|
83 | +- lo riduciamo fino a che non diventa uguale al più grande dei suoi vicini, nel nostro caso `8` si riduce a `6` mediante 2 azionamenti; |
|
83 | 84 | - torniamo al punto 1 |
84 | 85 | |
85 | -Naturalmente dobbiamo fare attenzione al caso in cui, per esempio, l'array fosse <code>5 8 8 6</code>. In questo caso infatti il massimo non è un solo elemento ma un intervallo di due elementi: dobbiamo quindi considerarli tutti come fossero un unico elemento (ed applicare le radiazioni all'intero intervallo). Dobbiamo inoltre fare attenzione a terminare l'algoritmo non appena tutti gli elementi diventano pari a zero. |
|
86 | +Naturalmente dobbiamo fare attenzione al caso in cui, per esempio, l'array fosse `5 8 8 6`. In questo caso infatti il massimo non è un solo elemento ma un intervallo di due elementi: dobbiamo quindi considerarli tutti come fossero un unico elemento (ed applicare le radiazioni all'intero intervallo). Dobbiamo inoltre fare attenzione a terminare l'algoritmo non appena tutti gli elementi diventano pari a zero. |
|
86 | 87 | |
87 | 88 | Un'implementazione alternativa di questa soluzione (più facile da codificare) considera tutti i numeri, da sinistra a destra, riducendoli a zero sequenzialmente (uno dopo l'altro) cercando di "includere" quanti più vicini possibile (ovvero: i vicini a destra fino a che non si incontra uno zero o la fine dell'array). È ragionevole convincersi che questa soluzione è equivalente a quella descritta sopra. |
88 | 89 | |
89 | -```[cpp] |
|
90 | +```c++ |
|
90 | 91 | const int MAXN = 1000; |
91 | 92 | |
92 | 93 | int compute(int N, int vec[MAXN + 2]) { |
... | ... | @@ -127,15 +128,15 @@ int main() { |
127 | 128 | } |
128 | 129 | ``` |
129 | 130 | |
130 | -### Escursione (<code>escursione</code>) — <span style="color: red;">difficile</span> |
|
131 | +### Escursione (`escursione`) - difficile |
|
131 | 132 | |
132 | 133 | Per risolvere questo problema è necessario avere dimestichezza con la ricorsione (per risolvere il problema in modo subottimale) o con i grafi (per la soluzione ottimale). |
133 | 134 | |
134 | 135 | #### Esplorazione completa |
135 | 136 | |
136 | -Una possibile soluzione subottimale è utilizzare una funzione ricorsiva che "esplora" la matrice. La funzione, dato un punto di partenza, visita ricorsivamente tutte le direzioni possibili (sinistra, sopra, destra, sotto) a patto che non si esca oltre i bordi. La funzione dovrà quindi avere tra i suoi parametri: la posizione (data da <code>i</code> e <code>j</code>) e la differenza di altitudine più alta trovata finora. |
|
137 | +Una possibile soluzione subottimale è utilizzare una funzione ricorsiva che "esplora" la matrice. La funzione, dato un punto di partenza, visita ricorsivamente tutte le direzioni possibili (sinistra, sopra, destra, sotto) a patto che non si esca oltre i bordi. La funzione dovrà quindi avere tra i suoi parametri: la posizione (data da `i` e `j`) e la differenza di altitudine più alta trovata finora. |
|
137 | 138 | |
138 | -Affinché la funzione non vada in ciclo infinito, è necessario mantenere globalmente una matrice booleana che indica se abbiamo visitato oppure no ciascuna cella. Dovremo quindi fare attenzione a impostare a <code>true</code> la posizione in cui stiamo entrando ricorsivamente, e soprattutto di reimpostare a <code>false</code> la posizione dalla quale stiamo uscendo ricorsivamente. Questa tecnica prende il nome di (backtracking)[https://en.wikipedia.org/wiki/Backtracking]. |
|
139 | +Affinché la funzione non vada in ciclo infinito, è necessario mantenere globalmente una matrice booleana che indica se abbiamo visitato oppure no ciascuna cella. Dovremo quindi fare attenzione a impostare a `true` la posizione in cui stiamo entrando ricorsivamente, e soprattutto di reimpostare a `false` la posizione dalla quale stiamo uscendo ricorsivamente. Questa tecnica prende il nome di (backtracking)[https://en.wikipedia.org/wiki/Backtracking]. |
|
139 | 140 | |
140 | 141 | Nel corpo della funzione dobbiamo controllare se la posizione attuale corrisponde con quella di arrivo e, se è così, dobbiamo memorizzare in una variabile globale il risultato migliore trovato finora (utilizzando il parametro della funzione che indica la differenza di altitudine più alta del percorso). |
141 | 142 | |
... | ... | @@ -143,7 +144,7 @@ Questa soluzione è corretta perché stiamo percorrendo tutti i possibili percor |
143 | 144 | |
144 | 145 | #### Versione ottimizzata |
145 | 146 | |
146 | -Un modo per ottimizzare questa soluzione è memorizzare per ciascuna posizione (oltre allo stato "visitato" / "non visitato") anche un'altra informazione: la migliore soluzione trovata per arrivare dal punto di partenza a quella posizione. Infatti, mantenendo questa informazione, possiamo **terminare anticipatamente** un intero ramo ricorsivo se notiamo che la soluzione "candidata" (quella che ci stiamo portando dietro nel parametro della funzione ricorsiva) è maggiore di quella che abbiamo salvato globalmente nella posizione corrente. Un accorgimento a cui dobbiamo far caso è che all'inizio del programma dobbiamo inizializzare la miglior soluzione trovata per ciascuna posizione ad un valore molto alto (è prassi definire una costante <code>INF</code> nel codice e impostarla a un valore sicuramente più alto della soluzione cercata). |
|
147 | +Un modo per ottimizzare questa soluzione è memorizzare per ciascuna posizione (oltre allo stato "visitato" / "non visitato") anche un'altra informazione: la migliore soluzione trovata per arrivare dal punto di partenza a quella posizione. Infatti, mantenendo questa informazione, possiamo **terminare anticipatamente** un intero ramo ricorsivo se notiamo che la soluzione "candidata" (quella che ci stiamo portando dietro nel parametro della funzione ricorsiva) è maggiore di quella che abbiamo salvato globalmente nella posizione corrente. Un accorgimento a cui dobbiamo far caso è che all'inizio del programma dobbiamo inizializzare la miglior soluzione trovata per ciascuna posizione ad un valore molto alto (è prassi definire una costante `INF` nel codice e impostarla a un valore sicuramente più alto della soluzione cercata). |
|
147 | 148 | |
148 | 149 | Questa soluzione dovrebbe essere molto più veloce ed è probabilmente sufficiente per prendere tutti i punti. |
149 | 150 | |
... | ... | @@ -151,7 +152,7 @@ Questa soluzione dovrebbe essere molto più veloce ed è probabilmente sufficien |
151 | 152 | |
152 | 153 | La soluzione ottimale a questo problema consiste nell'usare una variazione dell'[Algoritmo di Dijkstra](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm). |
153 | 154 | |
154 | -```[cpp] |
|
155 | +```c++ |
|
155 | 156 | #define INFTY 1000000000 |
156 | 157 | |
157 | 158 | const int MAXH = 100; |
... | ... | @@ -213,4 +214,4 @@ int main() { |
213 | 214 | solve(t); |
214 | 215 | } |
215 | 216 | } |
216 | -``` |
|
... | ... | \ No newline at end of file |
0 | +``` |