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
+```