Anais do 13O Encontro de Iniciação Científica e Pós-Graduação do ITA – XIII ENCITA / 2007 Instituto Tecnológico de Aeronáutica, São José dos Campos, SP, Brasil, Outubro, 01 a 04, 2007. ALGORITMOS EXATOS E PROBABILÍSTICOS PARA PROBLEMA DE CORTE E SEQÜENCIAMENTO Cesário Bruno de Barros Martins Instituto Tecnológico de Aeronáutica – Rua H8C, nº205, S. José dos Campos, SP, Brasil, CEP 12228-461 Bolsista PIBIC-CNPq [email protected] Nei Yoshihiro Soma Instituto Tecnológico de Aeronáutica – Pr. Mal. Eduardo Gomes, 50, S. José dos Campos, SP, Brasil, CEP 12228-900 [email protected] Resumo. Com base em algoritmos e em técnicas computacionais conhecidas, modela - se abstratamente situações de tal forma que a sua solução torne – se mais eficiente. Técnicas, como programação dinamica e iterações, e algoritmos, como o algoritmo de Dijkstra – para calcular menor caminho de uma única origem – e o algoritmo de Ford-Fulkerson – para calcular o menor custo de um certo fluxo em rede – são abordados. Palavras chave: programação dinâmica, grafos, bicoloração, Dijkstra, Ford-Fulkerson 1. Introdução Após o advento da computação, a capacidade humana de registrar e manipular dados em grandes quantidades com precisão e rapidez cresceu intensamente. A manipulação da imensa quantidade de informação que tem-se hoje em dia só e possível graças à evolução da informática. Há mais de 2000 anos o homem tenta produzir algo que o auxilie de alguma forma. Na história da computação, observa-se várias etapas até chegar no nível da tecnologia de hoje em dia. O modelo dos computadores atuais segue a proposta do matemático húngaro John Von Neuman (1903 – 1957) na qual define um computador seqüencial digital cujo processamento das informações é feito passo a passo, caracterizando um comportamento determinístico, isto é, os mesmos dados de entrada produzem sempre a mesma resposta. Paralelamente à evolução das máquinas, surgiu a evolução dos algoritmos para resolução de problemas. Podese definir um algoritmo como uma seqüência não ambígua de instruções que é executada até que determinada condição se verifique. Não adianta a existência de uma supermáquina se não sabe-se quão bem utiliza – lá. O estudo dos algoritmos permite encontrar passos bem definidos para a resolução de um certo tipo de problema. Um bom algoritmo possui três importantes características: 1. O algoritmo realmente é correto. Usando este algoritmo em qualquer conjunto de dados relacionados ao problema sempre fornecerá uma resposta correta. 2. O algoritmo é eficiente, isto é, ele procura fazer apenas passos necessários para a resolução do problema, diminuendo assim o tempo de execução. 3. O algoritmo utiliza apenas a memória necessária. O conhecimento de algoritmos já estudados é de enorme importância, pois muitos problemas computacionais são modeláveis por eles. Abstraindo um problema, pode-se resolver-lo de diversas formas, e, cabe a nós escolher a mais eficiente. A seguir, utiliza-se técnicas computacionais e algoritmos já conhecidos, descritos por Cormen[1] e Jungnickel[2], para se modelar abstratamente uma situação de tal forma que a solução do problema seja correta e eficiente. As situações abordadas fazem parte de um acervo de problemas e podem ser acessadas pelo site da Universidad de Valladolid[3] ou no site da XI Maratona de programação dos alunos da USP[4]. 2. Situação 1 Nome original: This Sentence is False Origem: ACM - Latin America - South America - 2002/2003 Site: http://acmicpc-live-archive.uva.es/nuevoportal/data/problem.php?p=2612 acessado em 08/2007 Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , 2.1. Descrição Em um reino tomado por intrigas e conspirações, o serviço secreto do rei encontrou um misterioso documento que pode fazer parte de um perverso esquema. O documento é composto por várias sentenças que sustentam a falsidade ou veracidade das outras sentenças. As sentenças são da forma “Sentença X é verdadeira/falsa”, em que X indica uma das sentenças. A tarefa é, com este documento em mãos, descobrir se as sentenças são consistentes e, se forem consistentes, o maior número de sentenças verdadeiras. Para melhor entendimento, exemplifica-se dois exemplos na Tab.1. Tabela 1. Exemplos da entrada de dados da 1º situação apresentada. Entrada Entrada 1 Sentença 1 é falsa. 5 Sentença 2 é falsa. Sentença 1 é falsa. Sentença 3 é verdadeira. Sentença 3 é verdadeira. Sentença 4 é falsa. 0 Saída Inconsistente Saída 3 2.2. Comentário Uma soluça ingênua para este problema seria, para cada sentença, supor que ela é falsa ou verdadeira e verificar a consistência das frases como um todo. Posto que este procedimento é da ordem exponencial, mostra-se uma solução elegante utilizando teoria dos grafos. 2.3. Solução Pelo enunciado, cada sentença relaciona – se com outra em uma relação de verdade ou falsidade. Se o documento for consistente, todas as frases são consistentes. Se pelo menos uma frase não for consistente, o documento será inconsistente. Para cada sentença, tem-se três variáveis: o estado da sentença de origem, o estado da sentença destino e o estado da afirmação. Sabe-se que cada estado pode ser verdadeiro ou falso. Indicando por V o estado verdadeiro e por F o estado falso, tem-se um total de 8 situações possíveis, mostradas na Fig.1. Sentença origem V V F F afirmação V F V F Sentença destino Sentença origem V F V Situações consistentes V F F V F afirmação V Sentença destino F F V F V Situações inconsistentes V F Figura 1 – Situações possíveis relacionando as sentenças. Assim, para as situações consistentes, nota-se que, se a afirmação possuir estado verdadeiro, os estados das sentenças de origem e destino serão iguais, e se a afirmação possuir estado falso, os estados das sentenças serão Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , diferentes. Este fato é uma invariante do problema, e pode-se basear nele para saber se o documento é consistente ou não. Baseado nas sentenças, cria-se um grafo em que os vértices são as sentenças e as arestas são as relações entre elas. Considere um grafo com sentenças consistentes. Suponha que existe um par de arestas ligando os mesmos vértices em sentidos opostos. Se estas arestas tiverem estados diferentes, os estados dos vértices têm que, ao mesmo tempo, ser iguais e diferentes. Como chega-se em um absurdo, tem-se que, em um grafo com sentenças consistentes, as arestas serão não-direcionadas. Usando esta construção, o grafo, formado pelas sentenças do exemplo 2 deste problema, será: Figura 2 - Grafo formado pela entrada do exemplo 2. Então, para todas as sentenças serem consistentes, o grafo tem que ser não-direcionado e o estado dos vértices ligados por uma aresta têm que ser consistente com o estado da aresta, isto é, têm que ser iguais, se o estado da aresta for “V”, ou diferentes, se o estado da aresta for “F”. Como, para completar o grafo, deve-se preencher os vértices com seus estados, isto é, com “V” ou “F”, pode-se tratar o problema como a coloração do grafo com as “cores” V ou F. Neste caso, o nosso problema de achar o maior número de frases verdadeiras, torna – se achar a maior quantidade de vértices que possuem a cor V. Para isso, nota-se que cada componente conexo do grafo é independente. Assim, após pintar um componente por inteiro, pode-se inverter as cores de todos os vértices a fim de deixar a componente na configuração de máximos vértices na cor V. Portanto, comentado todos os passos, pode-se descrever a solução da questão da seguinte maneira: 1 – Montar o grafo cujos vértices são os números de 1 a N. 2 – Traçar as arestas de acordo com o estado da afirmação que relaciona as sentenças. Se existir duas sentenças relacionando os mesmos vértices com afirmações diferentes, o documento é inconsistente. 3 – Executar um algoritmo de busca no grafo para percorrer cada componente conexo. Enquanto percorre cada componente, pinta – se os vértices de tal forma que as sentenças fiquem consistentes. Caso não exista uma configuração que vá de acordo com a consistência das frases, o documento será inconsistente. Escolhe – se a configuração com o maior número de vértices com a cor V. 4 – Caso o documento seja consistente, calcula a maior quantidade de vértices com a cor V. Caso contrário, apenas imprimi que o documento é inconsistente. Para o algoritmo de busca do passo 3, pode ser usado o algoritmo BFS (Breadth First Search – busca em largura em inglês) que determina o menor caminho de uma origem a um dado vértice em um grafo não ponderado. Esse algoritmo possui O(V^2) se o grafo for implementado na estrutura de matriz de adjacência. 3. Situação 2 Nome original: Overlaying Maps Origem: ACM - Asia - Dhaka - 2004/2005 Site: http://acmicpc-live-archive.uva.es/nuevoportal/data/problem.php?p=3013 acessado em 08/2007 3.1. Descrição Na figura 3, pode-se observar dois retângulos ABCD e A1B1C1D1 que representam mapas de escalas diferentes de AD uma mesma área retangular, isto é, AB A B A D . Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , y D1 D C Ponto E denota a mesma localização geográfica nos dois mapas. C1 A1 B1 A B x Figura 3 – Retângulos que representam mapas de uma mesma área retangular De acordo com os matemáticos, sempre existirá um ponto que representa a mesma locação geográfica nos dois mapas. Dados a orientação de dois mapas de uma mesma área retangular, a tarefa é achar este ponto especial. Assume – se que o ponto A encontra – se na origem e os lados do maior mapa são paralelos aos eixos x e y. 3.2. Comentário Para este problema, pode-se pensar em uma fórmula geométrica que calcula exatamente a localização do ponto especial. Mas, na solução proposta, usa-se a técnica de iterações. 3.3. Solução Pelos dados do problema, tem-se os dois primeiros retângulos que são mapas de uma mesma área retangular. Pela descrição do problema, se houvesse um terceiro retângulo A2B2C2D2 dentro do segundo, representando a mesma área retangular, iria existir um ponto especial tal como descrito anteriormente. Se a proporção entre o retângulo A2B2C2D2 e A1B1C1D1 for igual à proporção entre os retângulos A1B1C1D1 e ABCD, e se a posição relativa entre os mesmos pares de retângulos forem iguais, o ponto especial relacionado aos pares de retângulos A2B2C2D2 e A1B1C1D1 será o mesmo ponto relacionado com os pares A1B1C1D1 e ABCD. y D1 D C D2 A2 C1 E A1 C2 B2 A B1 B x Figura 4 – Figura que mostra o retângulo A2B2C2D2, criado após a primeira iteração Esse raciocínio pode ser utilizado diversas vezes. A cada iteração, pode-se relacionar os dois últimos retângulos e criar um novo que possui o mesmo ponto especial. Assim, existirão vários retângulos e apenas um ponto que representa a mesma posição geográfica em todos eles. Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , y D1 D C C1 A1 A B1 B x Figura 5 – Vários retângulos criados a partir de iterações sucessivas Nota-se que, a cada iteração, diminui-se o tamanho do novo retângulo. Então, para achar qual o ponto especial, itera-se diversas vezes até que o retângulo criado seja tão pequeno que os quatro pontos que o definem sejam praticamente iguais. Assim, a solução do problema será qualquer um dos quatro pontos do último retângulo gerado. 4. Situação 3 Nome original: Back to the Future Origem: X Maratona de Programação dos Alunos da USP - 2006 Site: http://www.ime.usp.br/~cef/Xmaratona/problems/prova.pdf acessado em 08/2007 4.1. Descrição Um grupo de amigos precisa fazer uma viagem aérea gastando a menor quantidade possível de dinheiro. Analisando as rotas aéreas disponíveis, perceberam que, em todas as rotas, o número de assentos disponíveis nos aviões era sempre o mesmo, a rota entre duas cidades sempre existia nos dois sentidos e cada rota poderia ser utilizada apenas uma vez. Dados as cidades pertencentes às rotas aéreas, a quantidade de amigos no grupo, a capacidade das rotas, o custo unitário da passagem aérea, e as cidades de origem e destino, a tarefa é , caso seja possível, calcular a menor quantidade possível de dinheiro que os amigos vão gastar para realizar a viagem. 4.2. Comentário Pela formulação deste problema, pode-se pensar em um simples algoritmo guloso para a solução: enquanto ainda existir amigos que não viajaram, calcular o trecho que possui o menor custo da cidade de origem à cidade destino e embarcar a maior quantidade possível de amigos nesse trecho. Pelo exemplo abaixo, em que os vértices são as cidades e as arestas representam as rotas ponderadas por seus custos unitários, nota-se que este pensamento não nos dá uma solução válida: Capacidade das rotas: 10 Quantidade de amigos: 15 Figura 6 – Exemplo em que o pensamento guloso nos dá uma resposta errada Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , Utilizando o pensamento guloso, seria impossível transportar os 20 amigos da cidade origem à cidade destino, pois, primeiramente, transporta-se 10 amigos no trecho com um custo de 11(trecho de custo menor possível) e depois não existiriam trechos disponíveis para transportar os outros 5 amigos que sobraram. Para este exemplo, o menor custo de transportar os 15 amigos possui um total valor de 225 (15 * 10 + 15 * 5) . Em uma solução válida, modela-se o problema usando a teoria de fluxo em redes e usa-se o algoritmo “minimum cost flow” para concluir a tarefa. 4.3. Solução O algoritmo “minimum cost flow” calcula o custo mínimo de mandar uma especificada quantidade de fluxo de certo destino a certa origem em um grafo de fluxo em rede. Então, para obter a solução, basta modelar o problema de modo que se possa utilizar tal algoritmo. Na modelagem, considera-se o número de amigos a serem transportados como sendo a quantidade especificada de fluxo. As cidades serão os vértices e as rotas serão as arestas do grafo de fluxo em rede. O custo e a capacidade de cada aresta serão, respectivamente, o preço da passagem e a capacidade de cada rota. Portanto, definindo por V o conjunto dos vértices e E o conjunto das arestas tem-se um fluxo em rede G = (V,E) que satisfaz as seguintes propriedades: - Restrição de capacidade: Para todo , , tem-se , , , em que , representa o fluxo que passa pela aresta que liga o vértice ao vértice e , representa a capacidade dessa aresta. - Anti-simetria oblíqua: todo , , , = , . - Conservação do fluxo: Sendo o vértice de origem e o vértice destino, para todo , tem-se ∑ , 0 , isto é, fluxo não é acumulado no vértice . Para o algoritmo do “minimum cost flow”, utiliza-se o algoritmo “Ford-Fulkerson” modificado em que os caminhos aumentantes são encontrados utilizando o algoritmo “Bellman-Ford” para encontrar o menor caminho entre a origem e o destino em termos de custo. Em termos de pseudo – código, a implementação do algoritmo fica: FORD-FULKERSON-MODIFICADO(G,s,t,d) Inicializar fluxo f com 0 enquanto (existir caminho aumentante p em termos de custo e o fluxo total f ≤ quantidade d de fluxo requerida) faça ampliar fluxo f ao longo de p retorna f Assim, utilizando o algoritmo em nosso fluxo em rede já modelado, tem-se a solução do problema. 5. Situação 4 Nome original: Fill Origem: Bulgarian National Olympiad in Informatics 2003 Site: http://acm.uva.es/p/v106/10603.html acessado em 08/2007 5.1. Descrição Existem 3 jarros de volumes a,b e c (inteiros positivos não maiores que 200). Inicialmente, os dois primeiros jarros encontram-se vazios e o terceiro encontra-se completamente cheio de água. É permitido passar água de um jarro a outro até que ou primeiro esvazie ou o segundo torne-se cheio. Sua tarefa é escrever um programa que calcula a menor quantidade total de água que precisa ser movimentada para que, no mínimo, um dos jarros tenha uma quantidade d(d é um inteiro positivo não maior que 200) de litros. Se não for possível medir d litros desta maneira, seu programa deve encontrar a maior quantidade de litros d’ < d que pode ser produzida e calcular a menor quantidade de litros movimentada pra produzir tal montante em, no mínimo, um dos jarros. 5.2. Comentário Em uma primeira abordagem, pode-se usar uma técnica chamada backtracking para simular todas as movimentações possíveis. Neste processo, pode-se chegar em uma mesma configuração dos três jarros por movimentações diferentes. Logo, teria-se que simular todas as movimentações possíveis, transformando nossa solução em um algoritmo não-polinomial. Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , A solução proposta baseia-se na teoria de grafos. Usa-se um algoritmo de achar o menor caminho entre vértices para que a tarefa seja concluida. 5.3. Solução No grafo, consideram-se as possíveis configurações simultâneas dos 3 jarros como sendo o conjunto de vértices e as movimentações necessárias para passar de uma configuração para outra como sendo o conjunto de arestas. Nota-se que, pelas regras de movimentação estabelecidas pelo problema, cada vértice no grafo possuirá, no mínimo, ou um jarro vazio ou um jarro cheio e o volume inicial de líquido se conserva. Estes fatos fazem com que o número de configurações simultâneas possíveis fique bastante reduzido, tornando assim o grafo bastante reduzido. Assim, sendo a,b e c respectivamente os volumes totais dos três jarros, na configuração inicial, tem-se: Capacidade: a Capacidade: b Capacidade: c Vol. utilizado: 0 Vol. utilizado: 0 Vol. utilizado: c Jarro 1 Jarro 2 Jarro 3 Figura 7 – Configuração inicial dos jarros. Depois de gerar o grafo, a tarefa consiste em achar o menor caminho entre os vértices inicial e qualquer vértice que, em sua configuração, possua um dos jarros com capacidade d. Para exemplificar, suponha os números 5,7 e 10 como sendo, respectivamente, a capacidade dos 3 jarros e a quantidade requerida d é 2. Assim, uma parte do grafo seria da forma: Figura 8 – Grafo relativo às configurações válidas. Neste exemplo, ao utilizar o Dijkstra, um algoritmo que calcula o caminho de menor custo em um grafo ponderado com valores não negativos, entre o vértice inicial(de configuração 0 – 0 – 10) e o vértice mais próximo que contém o número d na configuração(no nosso caso, vértice 5 – 2 – 3), será calculado um valor de 12 como menor caminho, que é o resultado correto. Caso todo o grafo seja percorrido e não seja encontrado um vértice com o número d em sua configuração, escolhese o vértice com o maior número d’ < d em sua configuração e o resultado será o menor caminho entre este vértice e o vértice inicial (valor já calculado). Portanto, comentado todos os passos, pode-se descrever a solução da questão da seguinte maneira: 1 – Montar o grafo descrito anteriormente. 2 – Usar o algoritmo de Dijkstra para encontrar o menor caminho entre o vértice inicial e o primeiro vértice que possuir o número d em sua configuração. Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , 3 – Caso o algoritmo percorra todo o grafo e não encontre tal vértice, encontrar um novo vértice com configuração que possua o maior número d’ < d e considerar como resposta o menor caminho entre o este vértice e o vértice inicial(valor já calculado pelo passo 2). O algoritmo de Dijkstra, implementado com uma matriz de adjacência possui, uma ordem de execução de O(V^2). Em termos de pseudo – código, tem-se: DIJKSTRA(G,w,s) { para cada vértice v ∈ a V faça d[v] ∞ π[v] nulo d[v] 0 enquanto Q 0 u extraia min Q S S u para cada vértice v adjacente a u faça se d[v] > d[u] + w(u,v) então d[v] d[u] + w(u,v) π[v] u } Nesta implementação d[v] é o vetor de distâncias de s até cada v, [v] identifica o vértice de onde se origina uma conexão até v de maneira a formar um caminho mínimo, S é um conjunto que representa todos os vértices v cujos caminhos mínimos desde s até já foram calculados e Q é um conjunto que contém todos os outros vértices, isto é, Q = V – S. 6. Situação 5 Nome original: Trigran Origem: Aquecimento para a XI Maratona de Programação dos Alunos da USP – 2007 Site: http://www.ime.usp.br/~cef/XImaratona/tar/prova.pdf acessado em 08/2007 6.1. Descrição O dono de uma fábrica de brinquedos decide criar um quebra-cabeça que utiliza apenas peças triangulares. Para a fabricação do jogo, uma peça de madeira, que possui a figura convexa a ser montada, é mandada a uma marcenaria para que sejam feitos os cortes. Sabendo que a marcenaria cobra pela quantidade total de madeira cortada, sua tarefa é criar um programa para o dono da fábrica de modo que, dado uma peça inicial (N pontos), determine como ela deve ser cortada em triângulos de forma a minimizar o custo de fabricação da peça. 6.2. Comentário Para a solução deste problema, utiliza-se a técnica de programação dinâmica, uma abordagem muito aplicada em algoritmos de otimização que se baseia nas idéias de subestruturas ótimas, isto é, uma solução ótima para o problema contém em seu interior soluções ótimas para subproblemas. 6.3. Solução Para resolver uma situação usando a técnica de programação dinâmica, deve-se encontrar a subestrutura ótima e depois usá – la para construir uma solução ótima para o problema a partir de soluções ótimas para subproblemas. Neste problema, tem-se, como entrada, um polígono convexo de N pontos (P1P2P3...PN) e deve-se calcular uma triangularização de tal forma que a soma das diagonais seja mínima. Avaliando a seqüência de pontos PiPi+1...Pj, onde i j, nota-se que qualquer triangularização ótima para o polígono convexo formado por esses pontos deve dividi – lo em dois poligonos convexos PiPi+1...Pk-1Pk e PiPkPk+1...Pj ,para algum i 1 k , que possuem uma triangularizações ótimas. Esse fato ocorre pois, caso contrário, tem-se uma situação melhor de triangularizar o polígono inicial, indo de encontro a nossa suposição. A figura abaixo ilustra essas observações: Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , Pj Pi Pi+1 Pk+1 Pk-1 Pk Figura 9 – Divisão ótima de um polígono convexo com os vértices PiPi+1...Pj Outro importante fato é que, caso a aresta PiPj não coincida com a aresta P1PN, isto é, caso i 1ej N, essa aresta não existe e deve-se adiciona – la na quantidade total da triangularização do polígono PiPi+1...Pj para que todas as diagonais sejam contabilizadas corretamente. Então, considerando m[i,j] como a soma mínima das diagonais do polígono formados pelos pontos PiPi+1...Pj, tem-se: m i, j 0, 0, min 1 , , , Em que d i, j 0, 1 0, 2 distância cartesiana entre os ponto P e P Com a implementação destas duas funções, resolve-se a tarefa requerida. Pela definição, a solução será m[1,N]. A ordem de execução deste algoritmo é da ordem de O(N^3). Em pseudo-código, tem-se: d(inteiros i,j){ se i=1 e j=N então retorna 0 se j - i<2 então retorna 0 retorna distância cartesiana entre os pontos Pi e Pj } TRIGRAN(pontos p) { para i=1 até i=N faça m[i,i] = 0 para i=1 até i=N-1 faça m[i,i+1] = 0 para h=2 até h=N-1 faça para i=1 até i=N-h faça j i+h para k=i+1 até k=j-1 faça m[i,j] = minimo(m[i,k]+m[k,j]+d(i,j)) } Anais do XIII ENCITA 2007, ITA, Outubro, 01-04, 2007 , 7. Conclusão Ao longo da iniciação científica, um estudo generalista em relação às áreas de computação foi produzido. Não focamos em apenas um tipo de resolução de problemas, e sim, focamos em estudar as mais diversas áreas possíveis. Deste modo, criamos uma habilidade de modelagem que nos permite utilizar diversas técnicas para um mesmo problema. Procuramos produzir esse fato neste relatório final. Abordagens diferenciadas foram utilizadas para a solução de 5 problemas. Esta habilidade é de grande valia no ramo da computação. Assim, os conhecimentos adquiridos neste projeto servirão de base para estudos futuros e para a solução de problemas em campeonatos de computação. 8. Agradecimentos Aos meus pais, por tudo. Aos meu amigos Helder Suzuki e Henry Hsu, pelas lições em computação. Ao Prof. Nei Yoshihiro Soma, pela orientação e dedicação neste trabalho de iniciação centifica. Ao Prof. Armando Gouveia, pelo apoio. Ao amigo Humberto Naves, pelos conhecimentos passados. Ao Conselho Nacional de Desenvolvimento Científico e Tecnológico (CNPq) pelo Programa Institucional de Bolsas de Iniciação Cientifica(PIBIC), do qual participei na condição de bolsista. 9. Referências [1] CORMEN, T.H., LEISERSON, C.E., RIVEST, R.L., STEIN, C., ALGORITMOS – Tradução da 2º edição americana – Teoria e Prática, Editora CAMPUS, Rio de Janeiro, 2002. [2] JUNGNICKEL, D., Graphs, Networks and Algorithms. Ed. Springer, New York, 1999. [3] Universidad de Valladolid – Problem Set Archive. Disponível em <http://amc.uva.es/p>. Acesso em agosto de 2007. [4] XI Maratona de programação dos alunos da USP. Disponível em <http://www.ime.usp.br/~cef/XImaratona>. Acesso em agosto de 2007.