Esercizio - costo alternato minimo e massimo di una sottosequenza (simulazione d'esame 2025-26, es. 2)
Fonte: simulazione d'esame 2025-26 di Algoritmi per l'Ingegneria, Ing. Informatica UniPD, seconda parte, esercizio 2 (17 punti).
Testo
Data una stringa di interi distinti (positivi, negativi o nulli), il suo costo alternato è definito come
(si pone ). Per esempio, se allora . Data in ingresso una stringa di interi, si vogliono determinare il costo alternato minimo e il costo alternato massimo delle sue sottosequenze.
- (a) [7 punti] Si formulino e si dimostrino due proprietà di sottostruttura ottima (per il problema di minimo e per quello di massimo) sullo spazio di sottoproblemi formato da tutti i suffissi di (si ricorda che e che denota convenzionalmente il suffisso vuoto). Si derivino le associate ricorrenze sui costi, denotando con il costo alternato minimo e con quello massimo per il sottoproblema . (Suggerimento: potrebbe essere necessario formulare proprietà "intrecciate".)
- (b) [6 punti] Si scriva lo pseudocodice di un algoritmo iterativo per il calcolo dei costi alternati minimo e massimo di sottosequenze di .
- (c) [4 punti] Si determini il numero esatto di confronti e operazioni aritmetiche tra interi eseguite dall'algoritmo.
Soluzione
Un'identità sul costo alternato
Aggiungere un elemento davanti a una stringa inverte il segno di tutti gli altri termini:
Questa identità vale anche per (). È il motivo per cui le due proprietà sono "intrecciate": il minimo per che inizia con si ottiene sottraendo a un massimo, e il massimo a un minimo. Per lavorare con i suffissi si aggiunge sempre davanti, quindi la scelta di includere o no dipende da una sottoistanza più corta, .
(a) Sottostruttura ottima
Sia una sottosequenza di di costo alternato minimo e una di costo massimo (esistono: le sottosequenze di sono finite).
Proprietà.
- Per : .
- Per : è la più "piccola" (per costo alternato) tra e .
- Per : è la più "grande" tra e .
Dimostrazione. Il punto 1 è banale: l'unica sottosequenza di è .
Punto 2. Sia e sia , con . Due casi.
- (2.a) oppure . Allora è anche una sottosequenza di . Ogni sottosequenza di è anche sottosequenza di , quindi ha costo minimo anche tra le sottosequenze di : è una .
- (2.b) . Allora con sottosequenza di , e per l'identità . Per minimizzare bisogna massimizzare : se non fosse una , sostituendo con (cut-and-paste) si otterrebbe una sottosequenza di di costo strettamente minore, contraddicendo la minimalità di . Quindi è una .
In entrambi i casi è uno dei due candidati e , che sono entrambi sottosequenze ammissibili di ; poiché è la migliore in assoluto, è la migliore delle due.
Punto 3. Simmetrico (si scambiano minimo e massimo).
Ricorrenze sui costi. Con e , l'identità sul costo dà
Si vuole e (essendo ). Poiché è una sottosequenza, per ogni . L'ipotesi che gli interi siano distinti non serve alla correttezza.
(b) Algoritmo
e dipendono solo da e dai valori e : si calcolano per decrescente da a e basta conservare gli ultimi due valori, invece di due vettori.
MIN_MAX_ALT(X)
n <- |X|
curr_m <- 0; curr_M <- 0 { m(n+1) = M(n+1) = 0 }
for i <- n downto 1 do
temp <- curr_m { salva m(i+1) prima di sovrascriverlo }
curr_m <- MIN(temp, x_i - curr_M) { m(i) = min{m(i+1), x_i - M(i+1)} }
curr_M <- MAX(curr_M, x_i - temp) { M(i) = max{M(i+1), x_i - m(i+1)} }
return (curr_m, curr_M)L'uso di temp è essenziale: il nuovo vuole il vecchio , ma curr_m è già stato aggiornato a . Senza temp si userebbe il valore sbagliato (errore classico). La correttezza segue dalle ricorrenze del punto (a) e dall'ordine di calcolo, che parte dal caso base e usa a ogni passo solo valori già calcolati.
Esempio
(). Si parte da e si scende:
| 6 | 0 | 0 | |
| 5 | 5 | ||
| 4 | −2 | ||
| 3 | 4 | ||
| 2 | −1 | ||
| 1 | 3 |
Risultato: , . Sottosequenze: il minimo è ; il massimo è (ogni elemento in posizione pari è negativo e la sottrazione lo rende positivo). Entrambi i valori coincidono con quelli ottenuti enumerando tutte le sottosequenze; l'algoritmo è stato verificato in Python con stringhe casuali di interi distinti, lunghezza fino a .
Se serve anche la sottosequenza, si salva per ogni un bit: quale dei due candidati realizza il minimo e quale il massimo (informazione addizionale, passo dello schema in Programmazione dinamica - Fibonacci, memoizzazione e schema del paradigmala programmazione dinamica (DP) evolve il divide and conquer: invece di risolvere di nuovo le stesse sottoistanze, le salva in una tabella. Fibonacci ricorsivo costa F_n - 1 addizioni (esponenziale), quello con tabella n - 1. Tre condizioni: sottostruttura, sottoistanze ripetute, spazio dei sottoproblemi ristretto. Bottom-up o memoizzazione (ricorsione + tabella globale). Per i problemi di ottimizzazione lo schema è: sottostruttura ottima, ricorrenza sui costi, informazione addizionale, calcolo della tabella, ricostruzione.Programmazione dinamica - Fibonacci, memoizzazione e schema del paradigma →).
(c) Numero esatto di operazioni
Il ciclo ha iterazioni. Ognuna esegue due sottrazioni ( e ) e due confronti (uno in MIN, uno in MAX); gli assegnamenti non si contano. Quindi
Per sono operazioni. L'algoritmo è lineare, e lo spazio ausiliario è .
Versione ripasso
Testo. , . Data di interi: (a) [7 punti] due proprietà di sottostruttura ottima (minimo e massimo) sui suffissi , , e ricorrenze per e (suggerimento: proprietà intrecciate); (b) [6 punti] pseudocodice iterativo; (c) [4 punti] numero esatto di confronti e operazioni aritmetiche.
- Identità chiave: (aggiungere davanti inverte tutti i segni). Perciò il minimo che usa si ottiene da un massimo di , e viceversa.
- Proprietà: ; per , è la migliore tra e ; la migliore tra e . Prova: se non usa è sottosequenza di e ottima lì; se , e deve massimizzare (cut-and-paste).
- Ricorrenze: ; ; . Sempre .
MIN_MAX_ALT(X)
n <- |X|; curr_m <- 0; curr_M <- 0
for i <- n downto 1 do
temp <- curr_m
curr_m <- MIN(temp, x_i - curr_M)
curr_M <- MAX(curr_M, x_i - temp)
return (curr_m, curr_M) temp serve perché usa il vecchio .
- Esempio : per vale . Quindi minimo con e massimo con tutta .
- Costo: iterazioni, ciascuna con sottrazioni e confronti: (lineare, spazio ).
- Errori tipici: usare
curr_mgià aggiornato nel calcolo di ; scrivere invece di ; dimenticare che è ammessa (quindi ); contare solo i confronti.