Neste artigo iremos explorar um tema conhecido como "Programação Dinâmica". A ideia será explorar a estrutura de um problema de forma a não calcular valores que já tenham sido calculados.

Este artigo terá essencialmente duas partes: uma em que discutimos a ideia base deste conceito; uma em que visitamos um conjunto de problemas clássicos de programação dinâmica.

Introdução

Começamos com uma leitura interessante e concisa:

Artigo: Capítulo 3.4.1 do livro Competitive Programming I

Ler das páginas 40 a 45 do livro.

Nota: Para quem não tem o livro

É normal que muitos não tenham uma cópia do livro. Felizmente a primeira edição do livro está disponível de graça neste link, a página 40 à 45 (ou da 52 à 57 do PDF).

Se quiserem mais uma introdução podem consultar outro artigo que inclui mais alguns exemplos.

*Artigo: Programação dinâmica

Artigo do Topcoder de programação dinâmica.

Problemas clássicos

Passamos agora a um overview de vários problemas clássicos deste tema.

Longest Increasing Subsequence

Neste problema é suposto, como o nome indica, calcular a maior subsequência crescente de uma sequência de inteiros. É um problema muito simples mas ao mesmo tempo interessante com muitas variantes possíveis. Um exemplo é o problema maior subsequência decrescente, que é uma adaptação trivial do problema. Um exemplo não trivial é do maior subsequência bitónica, ou seja, que é estritamente crescente até um certo ponto e a partir dai é estritamente decrescente. Mas afinal como se resolve?

Artigo: Capítulo 3.4.2 do livro Competitive Programming I

Ler das páginas 45 a 46 do livro (ou 57 a 58 do PDF).

O algoritmo visto tem complexidade temporal $O(N^2)$, mas na verdade conseguimos fazer muito melhor, eis um artigo que mostra uma forma de resolver este problema em $O(N\log(N))$:

*Artigo: LIS em $O(N\log(N))$

Um artigo interessante sobre um melhoramento do algoritmo mais simples para o LIS.

Uma nota curiosa, um problema deste estilo saiu numa prova de Seleção das ONI, nomeadamente o A de 2015, Tragédia Grega. Embora a solução seja um pouco mais complicada do que as técnicas apresentadas aqui, é um bom exemplo de como este problema é versátil.

Coin Change

O problema do troco das moedas pergunta para um dado valor $V$ e um conjunto de valores de $N$ moedas, qual o mínimo número de moedas necessário para fazer a quantia $V$. Vejamos como o resolver:

Artigo: Capítulo 3.4.2 do livro Competitive Programming I

Ler das páginas 46 a 47 do livro (ou 58 a 59 do PDF).

0-1 Knapsack

Dado um conjunto de itens, cada um com um peso e valor associado, é pedido para determinar o conjunto de itens com maior soma de valores, sendo que o peso total desses itens não pode exceder um determinado inteiro $W$. Uma possível solução:

*Artigo: 0-1 knapsack

Um artigo com uma discussão sobre o problema da mochila.

2D Maximum Sum

Dada uma matriz de inteiros, é pedido para calcular o sub-retângulo cuja soma seja a maior possível. Veremos como fazê-lo.

Artigo: Capítulo 3.4.2 do livro Competitive Programming I

Ler das páginas 47 a 49 do livro (ou 59 a 61 do PDF).

Na próxima secção veremos um método que nos permite otimizar a solução vista.

* 1D Maximum Sum

Este problema, também conhecido como maximum subarray problem, pede para calcular a subsequência contígua de uma dada sequência de inteiros cuja soma seja a maior possível. Vejamos que é possível fazê-lo em $O(N)$ com um algoritmo conhecido por "algoritmo de Kadane".

*Artigo: Maximum subarray problem

Um artigo com várias formas de abordar o problema em questão.

Agora fica o desafio: como usar este algoritmo para tornar a solução do problema a duas dimensões de $O(N^4)$ para $O(N^3)$, node $N$ é a dimensão da matriz do input.

Aplicações

Finalmente, veremos alguns problemas para treinarem.

Problema 1: Exact Change

Problema do Kattis, exactchange2 - Exact Change

*Problema 2: Restaurant Orders

Problema do Kattis, orders - Restaurant Orders

*Problema 3: Digit Sum

Problema do Kattis, digitsum - Digit Sum

Conclusão

Há muitos outros exemplos clássicos que podíamos falar, por exemplo o Edit Distance ou o Longest Common Subsequence, mas com estes exemplos já têm uma boa ideia do que é a programação dinâmica e já podem começar a experimentar problemas mais complicados.