resources/2018_Territoriali_soluzioni.md
... ...
@@ -0,0 +1,216 @@
1
+## Soluzioni — Selezione Territoriali 2018
2
+Di seguito spiegheremo brevemente come risolvere i quattro task delle selezioni territoriali 2018.
3
+
4
+### Festa canina (<code>party</code>) — <span style="color: #1dd01d;">molto facile</span></h3>
5
+
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
+
8
+Una semplice implementazione in C++ è la seguente:
9
+```[cpp]
10
+int main() {
11
+ int T;
12
+ cin >> T;
13
+ for (int t = 1; t <= T; t++) {
14
+ int N; cin >> N;
15
+ int sum = 0;
16
+ for (int i = 0; i < N; i++) {
17
+ int x;
18
+ cin >> x;
19
+ if (x > 0)
20
+ sum += x;
21
+ }
22
+ cout << "Case #" << t << ": " << sum << endl;
23
+ }
24
+}
25
+```
26
+
27
+### Antivirus (<code>antivirus</code>) — <span style="color: green;">facile</span>
28
+
29
+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
+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
+
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
+
35
+```[cpp]
36
+void solve(int t) {
37
+ int N1, N2, N3, N4;
38
+ cin >> N1 >> N2 >> N3 >> N4;
39
+
40
+ int M;
41
+ cin >> M;
42
+
43
+ string F1, F2, F3, F4;
44
+ cin >> F1 >> F2 >> F3 >> F4;
45
+
46
+ for (int i = 0; i + M <= N1; i++)
47
+ for (int j = 0; j + M <= N2; j++)
48
+ for (int k = 0; k + M <= N3; k++)
49
+ for (int l = 0; l + M <= N4; l++) {
50
+ bool match = true;
51
+
52
+ for (int m = 0; m < M; m++)
53
+ if (F1[i + m] != F2[j + m] || F2[j + m] != F3[k + m] || F3[k + m] != F4[l + m])
54
+ match = false;
55
+
56
+ if (match) {
57
+ cout << "Case #" << t << ": " << i << " " << j << " " << k << " " << l << endl;
58
+ return;
59
+ }
60
+ }
61
+}
62
+
63
+int main() {
64
+ int T;
65
+ cin >> T;
66
+
67
+ for (int t = 1; t <= T; t++) {
68
+ solve(t);
69
+ }
70
+}
71
+```
72
+
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
+
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
+
77
+### Radioanalisi fossile (<code>xray</code>) — <span style="color: blue;">medio</span>
78
+
79
+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
+- 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;
83
+- torniamo al punto 1
84
+
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
+
87
+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
+```[cpp]
90
+const int MAXN = 1000;
91
+
92
+int compute(int N, int vec[MAXN + 2]) {
93
+ int risp = 0;
94
+ for (int i = 1; i <= N; i++) {
95
+ while (vec[i]) {
96
+ risp++;
97
+ for (int j = i; j <= N; j++) {
98
+ if (!vec[j]) break;
99
+ vec[j]--;
100
+ }
101
+ }
102
+ }
103
+
104
+ return risp;
105
+}
106
+
107
+void solve(int t) {
108
+ int N;
109
+ cin >> N;
110
+ int vec[MAXN + 2];
111
+
112
+ vec[0] = vec[N + 1] = 0;
113
+ for (int i = 1; i <= N; i++) {
114
+ cin >> vec[i];
115
+ }
116
+
117
+ cout << "Case #" << t << ": " << compute(N, vec) << endl;
118
+}
119
+
120
+int main() {
121
+ int T;
122
+ cin >> T;
123
+
124
+ for (int t = 1; t <= T; t++) {
125
+ solve(t);
126
+ }
127
+}
128
+```
129
+
130
+### Escursione (<code>escursione</code>) — <span style="color: red;">difficile</span>
131
+
132
+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
+#### Esplorazione completa
135
+
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
+
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
+
140
+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
+Questa soluzione è corretta perché stiamo percorrendo tutti i possibili percorsi che partono dalla posizione di partenza ed arrivano a quella di arrivo senza passare due volte per lo stesso punto. Tuttavia, impiega *troppo* tempo! In una griglia 100x100 non basterebbero tutte le ore di gara a calcolare la soluzione. In realtà, non basterebbe nemmeno un milione di anni! Un'illustrazione di questo fenomeno (che prende il nome di *crescita esponenziale*) si può trovare [in questo simpatico video](https://www.youtube.com/watch?v=Q4gTV4r0zRs).
143
+
144
+#### Versione ottimizzata
145
+
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
+
148
+Questa soluzione dovrebbe essere molto più veloce ed è probabilmente sufficiente per prendere tutti i punti.
149
+
150
+#### Soluzione ottima
151
+
152
+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
+```[cpp]
155
+#define INFTY 1000000000
156
+
157
+const int MAXH = 100;
158
+const int MAXW = 100;
159
+
160
+int A[MAXH + 2][MAXW + 2];
161
+int D[MAXH + 2][MAXW + 2];
162
+int H, W;
163
+
164
+int compute() {
165
+ priority_queue<pair<int, pair<int, int>>> q;
166
+
167
+ q.push(make_pair(0, make_pair(1, 1)));
168
+
169
+ while (!q.empty()) {
170
+ auto top = q.top();
171
+ q.pop();
172
+
173
+ if (D[top.second.first][top.second.second] != INFTY)
174
+ continue;
175
+
176
+ D[top.second.first][top.second.second] = -top.first;
177
+
178
+ for (int i = -1; i <= 1; i++) {
179
+ for (int j = -1; j <= 1; j++) {
180
+ if (i * j == 0) {
181
+ q.push(make_pair(
182
+ -max(-top.first,
183
+ abs(A[top.second.first][top.second.second] -
184
+ A[top.second.first + i][top.second.second + j])),
185
+ make_pair(top.second.first + i, top.second.second + j)));
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return D[H][W];
191
+}
192
+
193
+void solve(int t) {
194
+ cin >> H >> W;
195
+
196
+ memset(D, 0, sizeof(D));
197
+
198
+ for (int i = 1; i <= H; i++) {
199
+ for (int j = 1; j <= W; j++) {
200
+ cin >> A[i][j];
201
+ D[i][j] = INFTY;
202
+ }
203
+ }
204
+
205
+ cout << "Case #" << t << ": " << compute() << endl;
206
+}
207
+
208
+int main() {
209
+ int T;
210
+ cin >> T;
211
+
212
+ for (int t = 1; t <= T; t++) {
213
+ solve(t);
214
+ }
215
+}
216
+```
... ...
\ No newline at end of file