Files
problemas-para-competicao-p…/longest-common-subsequence/arthur-dolival-longest-common-subsequence-tutorial.tex
2026-05-10 13:44:44 -03:00

115 lines
3.9 KiB
TeX

\documentclass[10pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{fullpage}
\usepackage{url}
\pagenumbering{gobble}
\usepackage{hyperref}
\usepackage{graphicx}
\input{statement/preamble.tex}
\title{ Tutorial: Maior Subsequência Comum}
\author{}
\date{}
\begin{document}
\maketitle
\section{Solução do Problema}
O problema da maior subsequência comum (\textit{Longest Common Subsequence}, LCS) pode ser resolvido por meio de \textit{programação dinâmica}.
A ideia central é decompor o problema em subproblemas menores, de forma que a solução ótima final seja construída a partir das soluções ótimas desses subproblemas.
\subsection{Definição do Subproblema}
Sejam duas strings \( s_1 \) e \( s_2 \), de tamanhos \( n \) e \( m \), respectivamente.
Definimos:
\[
dp[i][j] = \text{o comprimento da maior subsequência comum entre } s_1[1..i] \text{ e } s_2[1..j].
\]
Isto é, \( dp[i][j] \) representa a resposta do problema considerando apenas os primeiros \( i \) caracteres de \( s_1 \) e os primeiros \( j \) caracteres de \( s_2 \).
\subsection{Função de Transição}
Ao analisar um par de posições \( (i, j) \), temos duas possibilidades principais:
\begin{itemize}
\item Se os caracteres atuais são iguais, isto é, \( s_1[i] = s_2[j] \), então podemos estender uma subsequência comum encontrada anteriormente:
\[
dp[i][j] = dp[i-1][j-1] + 1.
\]
\item Caso contrário, não é possível usar ambos os caracteres ao mesmo tempo, então devemos escolher o melhor resultado entre ignorar um dos dois:
\[
dp[i][j] = \max(dp[i-1][j], \, dp[i][j-1]).
\]
\end{itemize}
Assim, a função de transição pode ser resumida como:
\[
dp[i][j] =
\begin{cases}
dp[i-1][j-1] + 1, & \text{se } s_1[i] = s_2[j] \\
\max(dp[i-1][j], \, dp[i][j-1]), & \text{caso contrário}
\end{cases}
\]
\subsection{Casos Base}
Os casos base seguem a lógica natural do problema:
\[
dp[0][j] = 0 \quad \forall j \ge 0
\]
\[
dp[i][0] = 0 \quad \forall i \ge 0
\]
Isto representa que, se uma das strings tiver tamanho zero, nenhuma subsequência comum pode ser formada.
\subsection{Recuperação da Subsequência Comum}
Após o preenchimento da matriz \( dp \), o valor \( dp[n][m] \) fornece o comprimento da maior subsequência comum entre \( s_1 \) e \( s_2 \).
No entanto, para obter também a subsequência em si, é necessário realizar um processo de \textit{backtracking} sobre a matriz.
A ideia consiste em iniciar a partir da posição \( (n, m) \) da matriz e caminhar em direção à origem.
Em cada passo:
\begin{itemize}
\item Se \( s_1[i] = s_2[j] \), então este caractere faz parte da LCS. Nesse caso, retrocedemos para \( (i-1, j-1) \).
\item Caso contrário, avançamos para o vizinho que contém o maior valor entre \( dp[i-1][j] \) e \( dp[i][j-1] \), pois esse vizinho indica o caminho que manteve o comprimento máximo da subsequência.
\end{itemize}
Como a subsequência é reconstruída de trás para frente, os caracteres encontrados devem ser adicionados no início da string resultante.
O algoritmo para recuperar uma subsequência comum de comprimento máximo é o seguinte:
\begin{verbatim}
int i = n; // ponteiro para s1
int j = m; // ponteiro para s2
string lcs = ""; // subsequência comum sendo reconstruída
// Enquanto ainda houver caracteres a considerar
while (i > 0 && j > 0) {
// Caso 1: os caracteres são iguais → fazem parte da LCS
if (s1[i - 1] == s2[j - 1]) {
lcs = s1[i - 1] + lcs; // adiciona no início da string
i--;
j--;
}
// Caso 2: se o valor de cima é maior, movemos para cima
else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
}
// Caso 3: caso contrário, movemos para a esquerda
else {
j--;
}
}
\end{verbatim}
Ao final desse processo, a string \texttt{lcs} conterá uma maior subsequência comum entre as duas strings.\end{document}