diff --git a/edit-distance/edit-distance-tutorial.pdf b/edit-distance/edit-distance-tutorial.pdf new file mode 100644 index 0000000..1299432 Binary files /dev/null and b/edit-distance/edit-distance-tutorial.pdf differ diff --git a/edit-distance/edit-distance-tutorial.tex b/edit-distance/edit-distance-tutorial.tex new file mode 100644 index 0000000..1a6f1c2 --- /dev/null +++ b/edit-distance/edit-distance-tutorial.tex @@ -0,0 +1,51 @@ +\documentclass[10pt]{article} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amsthm,amssymb} +\usepackage{fullpage} +\usepackage{url} +\pagenumbering{gobble} +\usepackage{hyperref} + +\title{ Tutorial: Distância de Edição} +\author{} +\date{} +\begin{document} +\maketitle +\section{Solução do Problema} + +O problema de encontrar o menor número de operações para transformar uma string em outra, classicamente conhecido como Distância de Edição (\textit{Edit Distance}), pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}. + +\subsection{Definição do Subproblema} + +Sejam $w_1$ e $w_2$ as duas strings de tamanhos $N$ e $M$, respectivamente. +Definimos o nosso estado da programação dinâmica como: + +$$dp[i][j] = \text{número mínimo de operações para transformar o sufixo } w_1[i \dots N-1] \text{ no sufixo } w_2[j \dots M-1].$$ + +Assim, $dp[0][0]$ representará a resposta final para as duas strings inteiras. + +\subsection{Função de Transição} + +Para determinar $dp[i][j]$, olhamos para os caracteres atuais $w_1[i]$ e $w_2[j]$ e tomamos uma decisão. + +Se os caracteres forem iguais ($w_1[i] == w_2[j]$), não precisamos realizar nenhuma operação. Apenas avançamos na análise de ambas as strings: + +$$dp[i][j] = dp[i+1][j+1]$$ + +Se os caracteres forem diferentes ($w_1[i] \neq w_2[j]$), temos três opções de edição. Avaliamos o resultado de aplicar cada uma delas e escolhemos a que produz o menor custo final. Cada operação tem um custo de $1$: + +$$dp[i][j] = 1 + \min \begin{cases} + dp[i+1][j] & \text{(Remoção do caractere } w_1[i]\text{)} \\ + dp[i+1][j+1] & \text{(Substituição de } w_1[i] \text{ por } w_2[j]\text{)} \\ + dp[i][j+1] & \text{(Inserção do caractere } w_2[j] \text{ em } w_1\text{)} + \end{cases}$$ + +\subsection{Casos Base} + +Os casos base ocorrem quando esgotamos os caracteres de uma das strings (ou seja, quando $i$ ou $j$ ultrapassam os limites): + +Se consumirmos toda a string $w_1$ ($i \ge N$), mas ainda restarem caracteres em $w_2$, a única forma de igualá-las é \textbf{inserir} todos os $M - j$ caracteres restantes: +$$dp[N][j] = M - j$$ + +Da mesma forma, se consumirmos toda a string $w_2$ ($j \ge M$), mas ainda restarem caracteres em $w_1$, a única forma de igualá-las é \textbf{remover} todos os $N - i$ caracteres que sobraram: +$$dp[i][M] = N - i$$\end{document} diff --git a/edit-distance/edit-distance.pdf b/edit-distance/edit-distance.pdf index c8227bd..7db2672 100644 Binary files a/edit-distance/edit-distance.pdf and b/edit-distance/edit-distance.pdf differ diff --git a/edit-distance/statement/tutorial.tex b/edit-distance/statement/tutorial.tex index e69de29..1b2e695 100644 --- a/edit-distance/statement/tutorial.tex +++ b/edit-distance/statement/tutorial.tex @@ -0,0 +1,38 @@ +\section{Solução do Problema} + +O problema de encontrar o menor número de operações para transformar uma string em outra, classicamente conhecido como Distância de Edição (\textit{Edit Distance}), pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}. + +\subsection{Definição do Subproblema} + +Sejam $w_1$ e $w_2$ as duas strings de tamanhos $N$ e $M$, respectivamente. +Definimos o nosso estado da programação dinâmica como: + +$$dp[i][j] = \text{número mínimo de operações para transformar o sufixo } w_1[i \dots N-1] \text{ no sufixo } w_2[j \dots M-1].$$ + +Assim, $dp[0][0]$ representará a resposta final para as duas strings inteiras. + +\subsection{Função de Transição} + +Para determinar $dp[i][j]$, olhamos para os caracteres atuais $w_1[i]$ e $w_2[j]$ e tomamos uma decisão. + +Se os caracteres forem iguais ($w_1[i] == w_2[j]$), não precisamos realizar nenhuma operação. Apenas avançamos na análise de ambas as strings: + +$$dp[i][j] = dp[i+1][j+1]$$ + +Se os caracteres forem diferentes ($w_1[i] \neq w_2[j]$), temos três opções de edição. Avaliamos o resultado de aplicar cada uma delas e escolhemos a que produz o menor custo final. Cada operação tem um custo de $1$: + +$$dp[i][j] = 1 + \min \begin{cases} + dp[i+1][j] & \text{(Remoção do caractere } w_1[i]\text{)} \\ + dp[i+1][j+1] & \text{(Substituição de } w_1[i] \text{ por } w_2[j]\text{)} \\ + dp[i][j+1] & \text{(Inserção do caractere } w_2[j] \text{ em } w_1\text{)} + \end{cases}$$ + +\subsection{Casos Base} + +Os casos base ocorrem quando esgotamos os caracteres de uma das strings (ou seja, quando $i$ ou $j$ ultrapassam os limites): + +Se consumirmos toda a string $w_1$ ($i \ge N$), mas ainda restarem caracteres em $w_2$, a única forma de igualá-las é \textbf{inserir} todos os $M - j$ caracteres restantes: +$$dp[N][j] = M - j$$ + +Da mesma forma, se consumirmos toda a string $w_2$ ($j \ge M$), mas ainda restarem caracteres em $w_1$, a única forma de igualá-las é \textbf{remover} todos os $N - i$ caracteres que sobraram: +$$dp[i][M] = N - i$$ \ No newline at end of file diff --git a/longest-increasing-subsequence-II/longest-increasing-subsequence-II-tutorial.pdf b/longest-increasing-subsequence-II/longest-increasing-subsequence-II-tutorial.pdf new file mode 100644 index 0000000..127557a Binary files /dev/null and b/longest-increasing-subsequence-II/longest-increasing-subsequence-II-tutorial.pdf differ diff --git a/longest-increasing-subsequence-II/longest-increasing-subsequence-II-tutorial.tex b/longest-increasing-subsequence-II/longest-increasing-subsequence-II-tutorial.tex new file mode 100644 index 0000000..ead5476 --- /dev/null +++ b/longest-increasing-subsequence-II/longest-increasing-subsequence-II-tutorial.tex @@ -0,0 +1,57 @@ +\documentclass[10pt]{article} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amsthm,amssymb} +\usepackage{fullpage} +\usepackage{url} +\pagenumbering{gobble} +\usepackage{hyperref} + +\title{ Tutorial: Maior subsequência Crescente II} +\author{} +\date{} +\begin{document} +\maketitle +\section{Solução do Problema} + +O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) possui uma solução clássica em $O(N^2)$, mas quando $N$ é grande, podemos otimizá-la para \textbf{$O(N \log N)$} combinando \textbf{programação dinâmica com busca binária}. A grande sacada dessa otimização é mudar a perspectiva: em vez de guardar o tamanho da maior subsequência que termina em um índice, guardamos o \textbf{menor valor final} possível para uma subsequência de um determinado comprimento. + +\subsection{Definição do Subproblema} + +Seja $a$ o nosso vetor de números inteiros de tamanho $N$. +Definimos o nosso estado da programação dinâmica como: + +$$d[l] = \text{o menor valor que encerra uma subsequência crescente de comprimento exato } l.$$ + +O vetor $d$ terá uma propriedade muito importante: ele estará sempre rigorosamente ordenado de forma crescente. A resposta final será o maior $l$ tal que $d[l] \neq \infty$. + +\subsection{Função de Transição} + +Para calcular $d[l]$, iteramos sobre cada elemento $a[i]$ da nossa sequência original. Como queremos que os elementos finais de cada comprimento sejam os menores possíveis, tentamos usar $a[i]$ para melhorar o nosso vetor $d$. + +Como $d$ está ordenado, podemos usar \textbf{busca binária} (\texttt{upper\_bound} ou \texttt{lower\_bound}) para encontrar rapidamente a posição $l$ onde $a[i]$ deve ser inserido. Se $a[i]$ for maior que o final de uma subsequência de tamanho $l-1$ ($d[l-1] < a[i]$) e, ao mesmo tempo, puder substituir um final pior de tamanho $l$ ($a[i] < d[l]$), nós o atualizamos: + +$$d[l] = a[i]$$ + +Isso significa que encontramos uma subsequência crescente de comprimento $l$ que termina em $a[i]$, e esse final é melhor (menor) do que o que tínhamos registrado anteriormente. + +\subsection{Casos Base} + +Para que a lógica de busca e as comparações iniciais funcionem sem estourar os limites, inicializamos o vetor $d$ de tamanho $N+1$ da seguinte forma: + +$$d[0] = -\infty$$ +$$d[l] = \infty \quad \text{para todo } 1 \le l \le N$$ + +Isso representa que uma subsequência de tamanho $0$ termina em um valor infinitamente pequeno, enquanto os outros comprimentos ainda não foram alcançados. + +\subsection{Recuperação da Decomposição (Elementos da subsequência)} + +Para recuperar os elementos exatos da LIS nesta abordagem otimizada, o vetor $d$ sozinho não basta (pois ele pode conter uma mistura de elementos de diferentes subsequências válidas ao longo do tempo). Precisamos de duas estruturas de rastreamento: +\begin{itemize} + \item $pos[l]$: armazena o \textbf{índice original} do elemento que atualmente ocupa $d[l]$. + \item $p[i]$: armazena o predecessor do elemento de índice $i$, assim como na versão $O(N^2)$. +\end{itemize} + +Durante a transição, sempre que atualizamos $d[l] = a[i]$, nós também registramos que esse elemento se encontra no índice $i$ original fazendo $pos[l] = i$. +Ao mesmo tempo, sabemos que o elemento imediatamente anterior a ele na subsequência é o elemento que termina o comprimento $l-1$. Logo, conectamos o predecessor: $p[i] = pos[l-1]$. + +Ao final de todas as iterações, sabemos que \texttt{ans} é o comprimento máximo alcançado. O índice do último elemento dessa subsequência estará em $cur = pos[\texttt{ans}]$. A partir desse $cur$, reconstruímos a sequência retrocedendo $cur = p[cur]$ até atingir $-1$, e por fim invertemos a lista para exibi-la na ordem correta.\end{document} diff --git a/longest-increasing-subsequence-II/longest-increasing-subsequence-II.pdf b/longest-increasing-subsequence-II/longest-increasing-subsequence-II.pdf index d8b285e..2a1c7a8 100644 Binary files a/longest-increasing-subsequence-II/longest-increasing-subsequence-II.pdf and b/longest-increasing-subsequence-II/longest-increasing-subsequence-II.pdf differ diff --git a/longest-increasing-subsequence-II/statement/tutorial.tex b/longest-increasing-subsequence-II/statement/tutorial.tex index e69de29..16128b7 100644 --- a/longest-increasing-subsequence-II/statement/tutorial.tex +++ b/longest-increasing-subsequence-II/statement/tutorial.tex @@ -0,0 +1,44 @@ +\section{Solução do Problema} + +O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) possui uma solução clássica em $O(N^2)$, mas quando $N$ é grande, podemos otimizá-la para \textbf{$O(N \log N)$} combinando \textbf{programação dinâmica com busca binária}. A grande sacada dessa otimização é mudar a perspectiva: em vez de guardar o tamanho da maior subsequência que termina em um índice, guardamos o \textbf{menor valor final} possível para uma subsequência de um determinado comprimento. + +\subsection{Definição do Subproblema} + +Seja $a$ o nosso vetor de números inteiros de tamanho $N$. +Definimos o nosso estado da programação dinâmica como: + +$$d[l] = \text{o menor valor que encerra uma subsequência crescente de comprimento exato } l.$$ + +O vetor $d$ terá uma propriedade muito importante: ele estará sempre rigorosamente ordenado de forma crescente. A resposta final será o maior $l$ tal que $d[l] \neq \infty$. + +\subsection{Função de Transição} + +Para calcular $d[l]$, iteramos sobre cada elemento $a[i]$ da nossa sequência original. Como queremos que os elementos finais de cada comprimento sejam os menores possíveis, tentamos usar $a[i]$ para melhorar o nosso vetor $d$. + +Como $d$ está ordenado, podemos usar \textbf{busca binária} (\texttt{upper\_bound} ou \texttt{lower\_bound}) para encontrar rapidamente a posição $l$ onde $a[i]$ deve ser inserido. Se $a[i]$ for maior que o final de uma subsequência de tamanho $l-1$ ($d[l-1] < a[i]$) e, ao mesmo tempo, puder substituir um final pior de tamanho $l$ ($a[i] < d[l]$), nós o atualizamos: + +$$d[l] = a[i]$$ + +Isso significa que encontramos uma subsequência crescente de comprimento $l$ que termina em $a[i]$, e esse final é melhor (menor) do que o que tínhamos registrado anteriormente. + +\subsection{Casos Base} + +Para que a lógica de busca e as comparações iniciais funcionem sem estourar os limites, inicializamos o vetor $d$ de tamanho $N+1$ da seguinte forma: + +$$d[0] = -\infty$$ +$$d[l] = \infty \quad \text{para todo } 1 \le l \le N$$ + +Isso representa que uma subsequência de tamanho $0$ termina em um valor infinitamente pequeno, enquanto os outros comprimentos ainda não foram alcançados. + +\subsection{Recuperação da Decomposição (Elementos da subsequência)} + +Para recuperar os elementos exatos da LIS nesta abordagem otimizada, o vetor $d$ sozinho não basta (pois ele pode conter uma mistura de elementos de diferentes subsequências válidas ao longo do tempo). Precisamos de duas estruturas de rastreamento: +\begin{itemize} + \item $pos[l]$: armazena o \textbf{índice original} do elemento que atualmente ocupa $d[l]$. + \item $p[i]$: armazena o predecessor do elemento de índice $i$, assim como na versão $O(N^2)$. +\end{itemize} + +Durante a transição, sempre que atualizamos $d[l] = a[i]$, nós também registramos que esse elemento se encontra no índice $i$ original fazendo $pos[l] = i$. +Ao mesmo tempo, sabemos que o elemento imediatamente anterior a ele na subsequência é o elemento que termina o comprimento $l-1$. Logo, conectamos o predecessor: $p[i] = pos[l-1]$. + +Ao final de todas as iterações, sabemos que \texttt{ans} é o comprimento máximo alcançado. O índice do último elemento dessa subsequência estará em $cur = pos[\texttt{ans}]$. A partir desse $cur$, reconstruímos a sequência retrocedendo $cur = p[cur]$ até atingir $-1$, e por fim invertemos a lista para exibi-la na ordem correta. \ No newline at end of file diff --git a/longest-increasing-subsequence/longest-increasing-subsequence-tutorial.pdf b/longest-increasing-subsequence/longest-increasing-subsequence-tutorial.pdf new file mode 100644 index 0000000..c08a258 Binary files /dev/null and b/longest-increasing-subsequence/longest-increasing-subsequence-tutorial.pdf differ diff --git a/longest-increasing-subsequence/longest-increasing-subsequence-tutorial.tex b/longest-increasing-subsequence/longest-increasing-subsequence-tutorial.tex new file mode 100644 index 0000000..58e53ed --- /dev/null +++ b/longest-increasing-subsequence/longest-increasing-subsequence-tutorial.tex @@ -0,0 +1,48 @@ +\documentclass[10pt]{article} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amsthm,amssymb} +\usepackage{fullpage} +\usepackage{url} +\pagenumbering{gobble} +\usepackage{hyperref} + +\title{ Tutorial: Maior subsequência Crescente} +\author{} +\date{} +\begin{document} +\maketitle +\section{Solução do Problema} + +O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) é um clássico que pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}. A ideia central é focar no final da subsequência: para cada elemento da sequência original, calculamos qual é a maior subsequência crescente que termina exatamente nele. + +\subsection{Definição do Subproblema} + +Seja $a$ o nosso vetor de números inteiros com $N$ elementos, indexado de $0$ a $N-1$. +Definimos o nosso estado da programação dinâmica como: + +$$d[i] = \text{o comprimento da maior subsequência crescente que termina obrigatoriamente no índice } i.$$ + +A resposta final para o tamanho da maior subsequência não será necessariamente $d[N-1]$, mas sim o maior valor alcançado em todo o vetor $d$, ou seja, $\max(d[i])$ para $0 \le i < N$. + +\subsection{Função de Transição} + +Para determinar o valor de $d[i]$, precisamos olhar para todos os elementos anteriores à posição $i$, ou seja, um índice $j$ tal que $0 \le j < i$. + +Se encontrarmos um elemento anterior que seja estritamente menor que o elemento atual ($a[j] < a[i]$), isso significa que podemos estender a subsequência que terminava em $j$ anexando o elemento $a[i]$ ao final dela. O novo comprimento seria $d[j] + 1$. +Avaliamos todos os possíveis índices $j$ válidos e mantemos o que gera o maior comprimento: + +$$d[i] = \max_{\substack{0 \le j < i \\ a[j] < a[i]}} \big( d[j] + 1 \big)$$ + +\subsection{Casos Base} + +O caso base é bastante intuitivo: na pior das hipóteses, qualquer elemento isolado forma, por si só, uma subsequência válida de comprimento $1$. Portanto, a nossa inicialização padrão para todos os estados é: + +$$d[i] = 1 \quad \text{para todo } 0 \le i < N$$ + +\subsection{Recuperação da Decomposição (Elementos da subsequência)} + +Como o problema exige a impressão dos elementos que formam a subsequência, precisamos reconstruir o caminho ótimo. Para isso, utilizamos um vetor auxiliar $p[i]$ (de \textit{parent} ou predecessor), inicializado com $-1$. + +Durante a transição, sempre que atualizamos $d[i]$ com um valor maior proveniente de um $d[j] + 1$, registramos o índice de origem definindo $p[i] = j$. + +Após calcularmos todo o vetor $d$, localizamos o índice \texttt{pos} que contém o valor máximo absoluto de $d$. A partir de \texttt{pos}, recuperamos os elementos da subsequência percorrendo o caminho reverso: adicionamos $a[\texttt{pos}]$ à nossa lista de resposta e saltamos para o predecessor atualizando $\texttt{pos} = p[\texttt{pos}]$. Repetimos o processo até que $\texttt{pos}$ seja $-1$. Como resgatamos os elementos do final para o começo, basta inverter a lista resultante para apresentar a subsequência na ordem crescente original.\end{document} diff --git a/longest-increasing-subsequence/longest-increasing-subsequence.pdf b/longest-increasing-subsequence/longest-increasing-subsequence.pdf index 4f5d246..a3d34f7 100644 Binary files a/longest-increasing-subsequence/longest-increasing-subsequence.pdf and b/longest-increasing-subsequence/longest-increasing-subsequence.pdf differ diff --git a/longest-increasing-subsequence/statement/tutorial.tex b/longest-increasing-subsequence/statement/tutorial.tex index e69de29..2a130fb 100644 --- a/longest-increasing-subsequence/statement/tutorial.tex +++ b/longest-increasing-subsequence/statement/tutorial.tex @@ -0,0 +1,35 @@ +\section{Solução do Problema} + +O problema de encontrar a maior subsequência crescente (\textit{Longest Increasing Subsequence} - LIS) é um clássico que pode ser resolvido de forma eficiente por meio de \textbf{programação dinâmica}. A ideia central é focar no final da subsequência: para cada elemento da sequência original, calculamos qual é a maior subsequência crescente que termina exatamente nele. + +\subsection{Definição do Subproblema} + +Seja $a$ o nosso vetor de números inteiros com $N$ elementos, indexado de $0$ a $N-1$. +Definimos o nosso estado da programação dinâmica como: + +$$d[i] = \text{o comprimento da maior subsequência crescente que termina obrigatoriamente no índice } i.$$ + +A resposta final para o tamanho da maior subsequência não será necessariamente $d[N-1]$, mas sim o maior valor alcançado em todo o vetor $d$, ou seja, $\max(d[i])$ para $0 \le i < N$. + +\subsection{Função de Transição} + +Para determinar o valor de $d[i]$, precisamos olhar para todos os elementos anteriores à posição $i$, ou seja, um índice $j$ tal que $0 \le j < i$. + +Se encontrarmos um elemento anterior que seja estritamente menor que o elemento atual ($a[j] < a[i]$), isso significa que podemos estender a subsequência que terminava em $j$ anexando o elemento $a[i]$ ao final dela. O novo comprimento seria $d[j] + 1$. +Avaliamos todos os possíveis índices $j$ válidos e mantemos o que gera o maior comprimento: + +$$d[i] = \max_{\substack{0 \le j < i \\ a[j] < a[i]}} \big( d[j] + 1 \big)$$ + +\subsection{Casos Base} + +O caso base é bastante intuitivo: na pior das hipóteses, qualquer elemento isolado forma, por si só, uma subsequência válida de comprimento $1$. Portanto, a nossa inicialização padrão para todos os estados é: + +$$d[i] = 1 \quad \text{para todo } 0 \le i < N$$ + +\subsection{Recuperação da Decomposição (Elementos da subsequência)} + +Como o problema exige a impressão dos elementos que formam a subsequência, precisamos reconstruir o caminho ótimo. Para isso, utilizamos um vetor auxiliar $p[i]$ (de \textit{parent} ou predecessor), inicializado com $-1$. + +Durante a transição, sempre que atualizamos $d[i]$ com um valor maior proveniente de um $d[j] + 1$, registramos o índice de origem definindo $p[i] = j$. + +Após calcularmos todo o vetor $d$, localizamos o índice \texttt{pos} que contém o valor máximo absoluto de $d$. A partir de \texttt{pos}, recuperamos os elementos da subsequência percorrendo o caminho reverso: adicionamos $a[\texttt{pos}]$ à nossa lista de resposta e saltamos para o predecessor atualizando $\texttt{pos} = p[\texttt{pos}]$. Repetimos o processo até que $\texttt{pos}$ seja $-1$. Como resgatamos os elementos do final para o começo, basta inverter a lista resultante para apresentar a subsequência na ordem crescente original. \ No newline at end of file diff --git a/longest-palindromic-subsequence/longest-palindromic-subsequence-tutorial.pdf b/longest-palindromic-subsequence/longest-palindromic-subsequence-tutorial.pdf new file mode 100644 index 0000000..25d377a Binary files /dev/null and b/longest-palindromic-subsequence/longest-palindromic-subsequence-tutorial.pdf differ diff --git a/longest-palindromic-subsequence/longest-palindromic-subsequence-tutorial.tex b/longest-palindromic-subsequence/longest-palindromic-subsequence-tutorial.tex new file mode 100644 index 0000000..58bd198 --- /dev/null +++ b/longest-palindromic-subsequence/longest-palindromic-subsequence-tutorial.tex @@ -0,0 +1,47 @@ +\documentclass[10pt]{article} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amsthm,amssymb} +\usepackage{fullpage} +\usepackage{url} +\pagenumbering{gobble} +\usepackage{hyperref} + +\title{ Tutorial: Maior Subsequência Palindrômica} +\author{} +\date{} +\begin{document} +\maketitle +\section{Solução do Problema} + +O problema de encontrar a Maior Subsequência Palindrômica (\textit{Longest Palindromic Subsequence} - LPS) pode ser resolvido de forma elegante utilizando \textbf{programação dinâmica em intervalos}. A ideia é analisar o problema de fora para dentro: comparamos os caracteres nas extremidades de uma substring e decidimos se eles farão parte do nosso palíndromo ou se devemos encolher o nosso intervalo de busca. + +\subsection{Definição do Subproblema} + +Seja $S$ a nossa string original de tamanho $N$, indexada de $0$ a $N-1$. +Definimos o nosso estado da programação dinâmica como: + +$$dp[i][j] = \text{o comprimento da maior subsequência palindrômica dentro da substring } S[i \dots j].$$ + +Assim, a resposta para o comprimento máximo em toda a string será encontrada em $dp[0][N-1]$, que engloba o intervalo completo do primeiro ao último caractere. + +\subsection{Função de Transição} + +Para calcular $dp[i][j]$, olhamos para os caracteres nas pontas do intervalo atual: $S[i]$ e $S[j]$. Temos dois cenários possíveis: + +1. \textbf{Os caracteres são iguais ($S[i] == S[j]$):} Eles podem formar as pontas de um palíndromo. Nesse caso, ganhamos $2$ de comprimento (um caractere de cada lado) e somamos ao melhor palíndromo que conseguimos formar no intervalo estritamente interno a eles. +$$dp[i][j] = dp[i+1][j-1] + 2$$ + +2. \textbf{Os caracteres são diferentes ($S[i] \neq S[j]$):} Como eles são diferentes, não podem formar as pontas do mesmo palíndromo simultaneamente. Precisamos descartar um dos lados e ver qual escolha nos dá o melhor resultado. Calculamos ignorando o caractere da esquerda (reduzindo para $i+1$) ou ignorando o da direita (reduzindo para $j-1$). +$$dp[i][j] = \max \big( dp[i+1][j], dp[i][j-1] \big)$$ + +\textit{Nota de implementação:} Para garantir que $dp[i+1][j-1]$ ou as outras dependências já estejam calculadas quando precisarmos delas, os laços devem processar os intervalos do menor para o maior, ou preenchendo o $i$ de trás para frente (de $N-1$ até $0$) e o $j$ de frente para trás (de $i+1$ até $N-1$). + +\subsection{Casos Base} + +Os casos base ocorrem para intervalos de tamanho 1 e intervalos inválidos (tamanho 0 ou negativo): + +Todo caractere isolado é, por definição, um palíndromo de comprimento 1. Portanto, a diagonal principal da nossa matriz de estados recebe $1$: +$$dp[i][i] = 1 \quad \text{para todo } 0 \le i < N$$ + +Para intervalos onde o início ultrapassa o fim (o que pode ocorrer durante a transição de $dp[i+1][j-1]$ quando $j = i+1$), o comprimento é zero: +$$dp[i][j] = 0 \quad \text{para todo } i > j$$\end{document} diff --git a/longest-palindromic-subsequence/longest-palindromic-subsequence.pdf b/longest-palindromic-subsequence/longest-palindromic-subsequence.pdf index 794ecd4..b9b9f37 100644 Binary files a/longest-palindromic-subsequence/longest-palindromic-subsequence.pdf and b/longest-palindromic-subsequence/longest-palindromic-subsequence.pdf differ diff --git a/longest-palindromic-subsequence/statement/tutorial.tex b/longest-palindromic-subsequence/statement/tutorial.tex index e69de29..8a853e2 100644 --- a/longest-palindromic-subsequence/statement/tutorial.tex +++ b/longest-palindromic-subsequence/statement/tutorial.tex @@ -0,0 +1,34 @@ +\section{Solução do Problema} + +O problema de encontrar a Maior Subsequência Palindrômica (\textit{Longest Palindromic Subsequence} - LPS) pode ser resolvido de forma elegante utilizando \textbf{programação dinâmica em intervalos}. A ideia é analisar o problema de fora para dentro: comparamos os caracteres nas extremidades de uma substring e decidimos se eles farão parte do nosso palíndromo ou se devemos encolher o nosso intervalo de busca. + +\subsection{Definição do Subproblema} + +Seja $S$ a nossa string original de tamanho $N$, indexada de $0$ a $N-1$. +Definimos o nosso estado da programação dinâmica como: + +$$dp[i][j] = \text{o comprimento da maior subsequência palindrômica dentro da substring } S[i \dots j].$$ + +Assim, a resposta para o comprimento máximo em toda a string será encontrada em $dp[0][N-1]$, que engloba o intervalo completo do primeiro ao último caractere. + +\subsection{Função de Transição} + +Para calcular $dp[i][j]$, olhamos para os caracteres nas pontas do intervalo atual: $S[i]$ e $S[j]$. Temos dois cenários possíveis: + +1. \textbf{Os caracteres são iguais ($S[i] == S[j]$):} Eles podem formar as pontas de um palíndromo. Nesse caso, ganhamos $2$ de comprimento (um caractere de cada lado) e somamos ao melhor palíndromo que conseguimos formar no intervalo estritamente interno a eles. +$$dp[i][j] = dp[i+1][j-1] + 2$$ + +2. \textbf{Os caracteres são diferentes ($S[i] \neq S[j]$):} Como eles são diferentes, não podem formar as pontas do mesmo palíndromo simultaneamente. Precisamos descartar um dos lados e ver qual escolha nos dá o melhor resultado. Calculamos ignorando o caractere da esquerda (reduzindo para $i+1$) ou ignorando o da direita (reduzindo para $j-1$). +$$dp[i][j] = \max \big( dp[i+1][j], dp[i][j-1] \big)$$ + +\textit{Nota de implementação:} Para garantir que $dp[i+1][j-1]$ ou as outras dependências já estejam calculadas quando precisarmos delas, os laços devem processar os intervalos do menor para o maior, ou preenchendo o $i$ de trás para frente (de $N-1$ até $0$) e o $j$ de frente para trás (de $i+1$ até $N-1$). + +\subsection{Casos Base} + +Os casos base ocorrem para intervalos de tamanho 1 e intervalos inválidos (tamanho 0 ou negativo): + +Todo caractere isolado é, por definição, um palíndromo de comprimento 1. Portanto, a diagonal principal da nossa matriz de estados recebe $1$: +$$dp[i][i] = 1 \quad \text{para todo } 0 \le i < N$$ + +Para intervalos onde o início ultrapassa o fim (o que pode ocorrer durante a transição de $dp[i+1][j-1]$ quando $j = i+1$), o comprimento é zero: +$$dp[i][j] = 0 \quad \text{para todo } i > j$$ \ No newline at end of file