Problema F - Tiled Wall |
Autor: André Restigo (Faculdade de Engenharia da Universidade do Porto)
Tipo de problema: Grids + Programação Dinâmica
Número de ficheiros de teste: 20
Este era um problema interessante e que acabou por ser o único que não foi resolvido por nenhuma equipa. Essencialmente pedia-nos para descobrir qual o tamanho mínimo dos retângulos que formavam a "grelha" completa.
Essencialmente, a nossa tarefa podia ser dividida em duas partes:
Comecemos pela primeira parte:
Seja N o número máximo de de linhas e/ou colunas. Se considerarmos apenas o eixo horizontal, temos exactamente (N+1) posições candidatas a "linhas divisoras". Podemos passar por cada uma delas e depois descer em cada uma dessas posições as N linhas e verificar se nalguma posição a linha dividiria uma tile da mesma cor. Algo muito semelhante pode ser feito depois no eixo vertical, resultando num método com complexidade O(N^2) para descobrir as "linhas divisoras", em ambos os eixos.
Depois disto, resta a segunda parte do problema. A primeira coisa a notar é que a largura do menor retângulo é independente da sua altura. Dito de outro modo, podemos primeiro encontrar o menor múltiplo que explica as linhas divisoras encontradas no eixo horizontal, e depois fazer o mesmo para o eixo vertical.
Mas o que que é realmente encontrar o menor múltiplo que explica as linhas divisoras? Encontrar as linhas divisoras vai-nos dar um conjunto de números (ex: {1, 2, 5, 7, 10}. O que queremos é encontrar o menor número M tal que:
Falta perceber como encontrar este número M. A hipótese mais exaustiva seria para todas as posições iniciais (no máximo N), tentar todos os múltiplos M possíveis (no máximo são N) e a partir daí iterar sobre as posições múltiplas a partir da inicial (no máximo N). Isto tudo daria um algoritmo cúbico O(N^3) que não passaria no tempo (embora se possam fazer muitos cortes).
Como resolver isto em tempo quadrático? Uma hipótese é usar programação dinâmica (PD). Consideremos o seguinte (existiam outras formulações possíveis):
can[M][i] = Posso chegar à posição i com o múltiplo M
Para um dado M, podemos fazer o seguinte:
- Para i entre 1 e N, can[M][i]=False [inicializar todas as posições a false] - Para i entre 1 e M, se linhadivisora[i], então can[M][i]=True [junto à borda] - Para i entre 0 e N-M, se can[M][i] e linhadivisora[i+M], então can[M][i+M]=True [múltiplos] - Para i entre N-M e M, se can[M][i]==True, então posso escolher o múltiplo M [junto à borda] - Caso a alínea anterior não se verifique, não posso usar esse M.
Cada M tem então uma maneira linear - O(N) - de ser verificado, e como temos no máximo N possíveis múltiplos, ficamos com um processo final de complexidade quadrática - O(N^2) - que passava no tempo!
Ligações interessantes: