Salta al contenuto
Note per Studenti Esercizio - sottosequenza antipalindroma di lunghezza massima (compito del 9 luglio 2025, es. 2)

Esercizio - sottosequenza antipalindroma di lunghezza massima (compito del 9 luglio 2025, es. 2)

Fonte: compito di Algoritmi per l'Ingegneria, Ing. Informatica UniPD, 9 luglio 2025, seconda parte, esercizio 2 (17 punti).

Testo

Una stringa Z=⟨z1,z2,…,zk⟩Z = \langle z_1, z_2, \dots, z_k \rangle si dice antipalindroma se ha lunghezza pari e, per k≥2k \ge 2, soddisfa la relazione z1+h≠zk−hz_{1+h} \ne z_{k-h} per 0≤h≤k−10 \le h \le k - 1. (Dunque Z=ϵZ = \epsilon è antipalindroma; le stringhe di lunghezza dispari non sono antipalindrome.)

Data in ingresso una arbitraria stringa XX di lunghezza n≥1n \ge 1 definita su un qualche alfabeto Σ\Sigma, si vuole determinare la lunghezza di una Sottosequenza Antipalindroma di Lunghezza Massima (SALM(X)\text{SALM}(X)). (Si osservi che tale lunghezza sarà sempre un numero pari.)

  • (a) [6 punti] Si provi una proprietà di sottostruttura ottima per il problema sullo spazio {Xi..j:1≤i≤j≤n}\{X_{i..j} : 1 \le i \le j \le n\} delle sottostringhe di XX.
  • (b) [3 punti] Si fornisca una ricorrenza sulla lunghezza della SALM(Xi..j)\text{SALM}(X_{i..j}) di una generica sottoistanza Xi..jX_{i..j}.
  • (c) [5 punti] Si fornisca lo pseudocodice bottom-up iterativo COMPUTE_SALM(X) che, data in ingresso una stringa X=⟨x1,…,xn⟩X = \langle x_1, \dots, x_n \rangle, restituisca la lunghezza di una SALM(X)(X), argomentandone la correttezza.
  • (d) [3 punti] Sia T(n)T(n) il numero di confronti tra caratteri dell'alfabeto Σ\Sigma eseguiti dall'algoritmo sviluppato al punto (c). Si valuti T(n)T(n) esattamente.

Soluzione

Cosa significa "antipalindroma"

Gli estremi simmetrici sono sempre diversi: z1≠zkz_1 \ne z_k, z2≠zk−1z_2 \ne z_{k-1}, e così via. Se kk è dispari l'elemento centrale (h=(k−1)/2h = (k-1)/2) dovrebbe essere diverso da sé stesso, impossibile: per questo le antipalindrome hanno lunghezza pari. Per esempio ⟨a,b⟩\langle a, b \rangle, ⟨a,b,c,d⟩\langle a, b, c, d \rangle e ⟨a,b,a,b⟩\langle a, b, a, b \rangle (coppie (a,b)(a,b) e (b,a)(b,a)) sono antipalindrome; ⟨a,b,c,a⟩\langle a, b, c, a \rangle no, perché z1=z4z_1 = z_4; ⟨a,b,a⟩\langle a, b, a \rangle no, perché ha lunghezza dispari. È la variante "opposta" della sottosequenza palindroma più lunga (Allineamento di stringhe e sottosequenza palindroma più lungadue problemi di DP su stringhe con la stessa struttura della LCS. Allineamento ottimo di due stringhe di DNA con matrice di score S e penalità di gap g: C(i,j) = max{C(i-1,j-1) + s(x_i,y_j), C(i-1,j) + g, C(i,j-1) + g}, C(i,0) = i g, C(0,j) = j g; Theta(mn) tempo, spazio Theta(min{m,n}) con un solo vettore se serve solo il costo. Sottosequenza palindroma più lunga (LPS) sullo spazio delle sottostringhe X_(i..j): p(i,j) = 2 + p(i+1,j-1) se x_i = x_j, altrimenti max{p(i+1,j), p(i,j-1)}; scansione per diagonali, n(n-1)/2 confronti.Allineamento di stringhe e sottosequenza palindroma più lunga →), con la stessa struttura: si tolgono i due estremi, quindi lo spazio naturale è quello delle sottostringhe.

(a) Proprietà di sottostruttura ottima

Proprietà. Per ogni 1≤i≤j≤n1 \le i \le j \le n esiste una Z=SALM(Xi..j)Z = \text{SALM}(X_{i..j}) tale che:

  1. se i=ji = j, Z=ϵZ = \epsilon;
  2. se j=i+1j = i + 1 e xi=xjx_i = x_j, Z=ϵZ = \epsilon;
  3. se j=i+1j = i + 1 e xi≠xjx_i \ne x_j, Z=⟨xi,xj⟩Z = \langle x_i, x_j \rangle;
  4. se j>i+1j > i + 1 e xi≠xjx_i \ne x_j, allora z1=xiz_1 = x_i, zk=xjz_k = x_j e Z2..k−1Z_{2..k-1} è una SALM(Xi+1..j−1)\text{SALM}(X_{i+1..j-1});
  5. se j>i+1j > i + 1 e xi=xjx_i = x_j, allora ZZ è una SALM(Xi+1..j)\text{SALM}(X_{i+1..j}) oppure una SALM(Xi..j−1)\text{SALM}(X_{i..j-1}) (la più lunga delle due).

Osservazione. Nel punto 4 si afferma che esiste una SALM che ha xix_i e xjx_j come estremi, non che tutte le SALM li abbiano: l'enunciato più forte sarebbe falso. Per esempio in X=⟨a,c,b⟩X = \langle a, c, b \rangle (x1=a≠b=x3x_1 = a \ne b = x_3) le SALM sono ⟨a,c⟩\langle a, c \rangle, ⟨a,b⟩\langle a, b \rangle e ⟨c,b⟩\langle c, b \rangle, tutte di lunghezza 22, ma solo ⟨a,b⟩\langle a, b \rangle ha z1=x1z_1 = x_1 e z2=x3z_2 = x_3. L'enunciato debole basta per un algoritmo corretto, come per la scelta greedy: serve che esista almeno una soluzione ottima con la proprietà.

Dimostrazione. I punti 1, 2 e 3 (sottostringhe di lunghezza 11 o 22) sono banali, e in quei casi ZZ è unica: una stringa di un solo carattere non ha sottosequenze antipalindrome non vuote (hanno lunghezza dispari); per due caratteri uguali nessuna coppia è diversa; per due caratteri diversi ⟨xi,xj⟩\langle x_i, x_j \rangle è antipalindroma e massima.

Sia ora j>i+1j > i + 1 (almeno 33 caratteri) e sia Z′=⟨xj1,…,xjk⟩Z' = \langle x_{j_1}, \dots, x_{j_k} \rangle una SALM qualsiasi di Xi..jX_{i..j}, con i≤j1<⋯<jk≤ji \le j_1 < \dots < j_k \le j.

Punto 4, xi≠xjx_i \ne x_j. Prima di tutto Z′≠ϵZ' \ne \epsilon: altrimenti ⟨xi,xj⟩\langle x_i, x_j \rangle sarebbe una sottosequenza antipalindroma di lunghezza 22, più lunga. Quindi k≥2k \ge 2. Si costruisce ZZ a partire da Z′Z' sostituendo, se j1>ij_1 > i, il primo carattere con xix_i (usando l'indice i<j1i < j_1) e, se jk<jj_k < j, l'ultimo con xjx_j (usando l'indice j>jkj > j_k). Gli indici restano strettamente crescenti, quindi ZZ è una sottosequenza di Xi..jX_{i..j} della stessa lunghezza kk. ZZ è ancora antipalindroma: le coppie interne non sono cambiate, e la coppia esterna è diventata (xi,xj)(x_i, x_j), di caratteri diversi (se nessuna sostituzione è avvenuta, era già j1=ij_1 = i, jk=jj_k = j e la coppia era (xi,xj)(x_i, x_j)). Quindi ZZ è una SALM con z1=xiz_1 = x_i e zk=xjz_k = x_j. La sottostringa interna Z2..k−1Z_{2..k-1} è antipalindroma e sottosequenza di Xi+1..j−1X_{i+1..j-1}. Se non fosse una SALM di Xi+1..j−1X_{i+1..j-1}, con il cut-and-paste si sostituirebbe con una SALM WW di Xi+1..j−1X_{i+1..j-1}: ⟨xi,W,xj⟩\langle x_i, W, x_j \rangle sarebbe antipalindroma (le coppie interne vengono da WW, quella esterna è (xi,xj)(x_i, x_j)) e più lunga di ZZ, contraddizione.

Punto 5, xi=xjx_i = x_j. Sia Z′Z' una SALM di Xi..jX_{i..j}. Se Z′=ϵZ' = \epsilon la tesi è ovvia (anche le sottostringhe hanno SALM vuote). Altrimenti z1≠zkz_1 \ne z_k per l'antipalindromia, quindi z1z_1 e zkz_k non possono essere entrambi uguali al comune valore xi=xjx_i = x_j. Se z1≠xiz_1 \ne x_i, l'indice ii non è usato (j1>ij_1 > i) e Z′Z' è sottosequenza di Xi+1..jX_{i+1..j}; se zk≠xjz_k \ne x_j, jk<jj_k < j e Z′Z' è sottosequenza di Xi..j−1X_{i..j-1}. In entrambi i casi Z′Z' è antipalindroma e per cut-and-paste deve essere una SALM della sottostringa in cui vive. Siccome le SALM delle due sottostringhe sono anche sottosequenze antipalindrome di Xi..jX_{i..j}, Z′Z' è la più lunga delle due. □\square

(b) Ricorrenza

Sia s(i,j)=∣SALM(Xi..j)∣s(i, j) = |\text{SALM}(X_{i..j})|, per 1≤i≤j≤n1 \le i \le j \le n:

s(i,j)={0i=j2j=i+1 ∧ xi≠xj0j=i+1 ∧ xi=xj2+s(i+1,j−1)j>i+1 ∧ xi≠xjmax⁡{s(i+1,j), s(i,j−1)}j>i+1 ∧ xi=xjs(i, j) = \begin{cases} 0 & i = j \\ 2 & j = i + 1 \ \land \ x_i \ne x_j \\ 0 & j = i + 1 \ \land \ x_i = x_j \\ 2 + s(i+1, j-1) & j > i + 1 \ \land \ x_i \ne x_j \\ \max\{s(i+1, j),\ s(i, j-1)\} & j > i + 1 \ \land \ x_i = x_j \end{cases}

I valori sono 00 o ottenuti con incrementi di 22: sono tutti pari. Si vuole s(1,n)s(1, n).

(c) Algoritmo bottom-up

Sia ℓ=j−i+1\ell = j - i + 1 la lunghezza di Xi..jX_{i..j}. I casi base sono ℓ=1\ell = 1 e ℓ=2\ell = 2. Per ℓ≥3\ell \ge 3 la cella (i,j)(i, j) dipende da (i+1,j−1)(i+1, j-1) (lunghezza ℓ−2\ell - 2) e da (i+1,j)(i+1, j) e (i,j−1)(i, j-1) (lunghezza ℓ−1\ell - 1): una scansione per diagonali di lunghezza crescente del triangolo superiore di una tabella S[1..n,1..n]S[1..n, 1..n] è coerente con le dipendenze.

COMPUTE_SALM(X)
  n <- length(X)
  for i <- 1 to n-1 do
    S[i, i] <- 0
    if x_i != x_{i+1} then S[i, i+1] <- 2 else S[i, i+1] <- 0
  S[n, n] <- 0                            { diagonali l = 1 e l = 2 }
  for l <- 3 to n do
    for i <- 1 to n-l+1 do
      j <- i + l - 1
      if x_i != x_j then S[i, j] <- S[i+1, j-1] + 2
      else S[i, j] <- MAX(S[i+1, j], S[i, j-1])
  return S[1, n]

Correttezza. Per induzione sulla lunghezza ℓ\ell della sottostringa: le diagonali ℓ=1,2\ell = 1, 2 contengono i valori dei casi base della ricorrenza (b); per ℓ≥3\ell \ge 3 al momento in cui si calcola S[i,j]S[i, j] le celle S[i+1,j−1]S[i+1, j-1], S[i+1,j]S[i+1, j] e S[i,j−1]S[i, j-1] appartengono a diagonali di lunghezza ℓ−2\ell - 2 e ℓ−1\ell - 1, già complete e corrette per ipotesi induttiva, e l'assegnamento realizza la ricorrenza. La ricorrenza è corretta per la proprietà (a). Per n=1n = 1 il primo ciclo è vuoto e si restituisce S[1,1]=0S[1,1] = 0. Valgono anche le scansioni "righe dalla nn alla 11" e "colonne con righe decrescenti". L'algoritmo è stato verificato in Python confrontandolo con l'enumerazione di tutte le sottosequenze su 500500 stringhe casuali (alfabeti di 22 e 33 simboli, lunghezza fino a 1010).

Esempio. X=abbacabX = abbacab (n=7n = 7). Tabella S[i,j]S[i, j] (triangolo superiore, la diagonale vale 00):

1: a 2: b 3: b 4: a 5: c 6: a 7: b
1: a 0 2 2 2 4 4 6
2: b 0 0 2 4 4 4
3: b 0 2 2 4 4
4: a 0 2 2 4
5: c 0 2 2
6: a 0 2
7: b 0

Per esempio S[2,3]=0S[2,3] = 0 perché x2=x3=bx_2 = x_3 = b; S[3,5]=2+S[4,4]=2S[3,5] = 2 + S[4,4] = 2 (b≠cb \ne c); S[2,6]=2+S[3,5]=4S[2,6] = 2 + S[3,5] = 4 (b≠ab \ne a); e S[1,7]=2+S[2,6]=6S[1,7] = 2 + S[2,6] = 6 (a≠ba \ne b). Una SALM di lunghezza 66 è ⟨a,b,b,c,a,b⟩\langle a, b, b, c, a, b \rangle (coppie (a,b),(b,a),(b,c)(a,b), (b,a), (b,c) tutte diverse), sottosequenza di abbacababbacab con indici 1,2,3,5,6,71, 2, 3, 5, 6, 7.

Se si vuole solo la lunghezza, come per la LPS, bastano tre vettori (la diagonale corrente e le due precedenti): spazio lineare.

(d) Numero di confronti

L'algoritmo esegue un confronto tra caratteri di Σ\Sigma per ogni sottostringa di lunghezza ℓ≥2\ell \ge 2, cioè per ogni coppia i<ji < j (i MAX confrontano interi, non caratteri):

T(n)=∑i=1n−1∑j=i+1n1=∑i=1n−1(n−i)=∑i′=1n−1i′=(n2)=n(n−1)2=Θ(n2).T(n) = \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} 1 = \sum_{i=1}^{n-1} (n - i) = \sum_{i'=1}^{n-1} i' = \binom{n}{2} = \frac{n(n-1)}{2} = \Theta(n^2).

Lo stesso risultato si ottiene per diagonali: n−1n - 1 confronti sulla diagonale ℓ=2\ell = 2 (nel primo ciclo) e n−ℓ+1n - \ell + 1 sulla diagonale ℓ≥3\ell \ge 3, in tutto ∑ℓ=2n(n−ℓ+1)=∑k=1n−1k\sum_{\ell=2}^{n} (n - \ell + 1) = \sum_{k=1}^{n-1} k. Per l'esempio n=7n = 7: 2121 confronti.

Versione ripasso

Testo. ZZ è antipalindroma se ha lunghezza pari e z1+h≠zk−hz_{1+h} \ne z_{k-h} per 0≤h≤k−10 \le h \le k - 1 (ϵ\epsilon lo è). Data XX di lunghezza n≥1n \ge 1, lunghezza di una SALM(X)(X) (sempre pari). (a) [6 punti] sottostruttura ottima sulle sottostringhe Xi..jX_{i..j}; (b) [3 punti] ricorrenza su s(i,j)s(i,j); (c) [5 punti] COMPUTE_SALM(X) bottom-up con correttezza; (d) [3 punti] numero esatto T(n)T(n) di confronti tra caratteri.

COMPUTE_SALM(X)
  n <- length(X)
  for i <- 1 to n-1 do
    S[i,i] <- 0
    if x_i != x_{i+1} then S[i,i+1] <- 2 else S[i,i+1] <- 0
  S[n,n] <- 0
  for l <- 3 to n do for i <- 1 to n-l+1 do
    j <- i + l - 1
    if x_i != x_j then S[i,j] <- S[i+1,j-1] + 2 else S[i,j] <- MAX(S[i+1,j], S[i,j-1])
  return S[1,n]
  • Correttezza: induzione sulla lunghezza ℓ\ell; (i,j)(i,j) dipende da diagonali ℓ−2\ell - 2 e ℓ−1\ell - 1, già complete. Valgono anche righe da nn a 11 o colonne con righe decrescenti.
  • Esempio X=abbacabX = abbacab: S[2,3]=0S[2,3] = 0, S[3,5]=2S[3,5] = 2, S[2,6]=4S[2,6] = 4, S[1,7]=6S[1,7] = 6 con ⟨a,b,b,c,a,b⟩\langle a, b, b, c, a, b \rangle (indici 1,2,3,5,6,71, 2, 3, 5, 6, 7).
  • Confronti: uno per ogni i<ji < j: T(n)=∑i=1n−1(n−i)=n(n−1)2T(n) = \sum_{i=1}^{n-1}(n-i) = \frac{n(n-1)}{2} (2121 per n=7n = 7). Spazio Θ(n2)\Theta(n^2), oppure lineare con tre vettori.
  • Errori tipici: confondere il caso uguale e diverso rispetto alla LPS (qui xi≠xjx_i \ne x_j permette di prendere entrambi gli estremi); inizializzare S[i,i+1]S[i,i+1] a 22 quando i caratteri sono uguali; contare i MAX come confronti tra caratteri; dimenticare che le stringhe di lunghezza dispari non sono ammesse.

Teoria collegata