OBI 2018 - Fase 2 - Programação Nível 2
Para conferir a prova na íntegra, clique aqui
Para ver todos os materiais incrível produzidos pela equipe NOIC para a OBI, veja a página de informática.
Fuga
Comentário Escrito por Arthur Lobo
Conhecimento prévio necessário:
Você quer derrubar armários de modo à maximizar a distância do menor caminho do ponto inicial até o final. A primeira vista, o problema pode parecer bem difícil, já que temos muitas opções para derrubar os armários e não é muito claro como podemos reduzi-las.
Primeiro, vamos dar uma olhada no exemplo:
Uma coisa que é possível perceber no exemplo é que, depois da derrubada dos armários, O Conde Olaf não somente escolhe o menor caminho do início até o fim, ele escolhe o único caminho do início até o fim, então ele não tem escolha: tem que seguir o caminho que Violet e Klaus "decidiram" que ele vai seguir.
E a ideia é justamente essa: decidir o caminho que ele vai seguir. Testando alguns casos na mão, é possível ver que podemos obrigar Conde Olaf a seguir qualquer caminho simples (que não repete nenhuma posição) que começa em e termina em .
Basicamente, para qualquer caminho, existe uma maneira de derrubar os armários de modo que esse seja o único caminho que leva do início até o final. Confira alguns exemplos de caminhos (não necessariamente ótimos) que podem ser montados num grid :
Sempre podemos ir virando or armários "enquanto" estamos fazendo o caminho. A ideia vai ser bloquear todas as opções de movimento em cada ponto e deixar apenas a desejada. Então agora o problema se reduz para encontrar o maior caminho simples que começa em e termina em .
Novamente, não é muito claro como, e se, podemos fazer isso de uma maneira simples e dentro do tempo limite: testar todos os caminhos, à primeira vista, parece ser muito lento. Mas na verdade, a quantidade de caminhos não é tão grande assim.
Na prática, ter um grid com os armários, é equivalente a ter um grid , porque sempre que estamos numa posição com e ímpares, vamos dar 2 passos seguidos na mesma direção, terminando em outra posição com e ímpares. Fazendo um código que testa a quantidade de caminhos começando em é .
Podemos testar todos os caminhos simples fazendo uma que desmarca um vértice como visitado toda vez que saímos dele. Será , com e sendo a coordenada dos pontos atuais e a distância percorrida desde o começo.
Nessa , nós vamos passar apenas pelos vértices com ambas as coordenadas ímpares, ou seja, vamos dar passos de 2 em 2 em cada direção (isso dá certo, já que o início e o final estão em coordenadas ímpares).
Primeiro chamamos e começamos resposta = 0; dentro de faremos:
- Marcamos como uma posição visitada: .
- Se e , fazemos , pois encontramos um possível caminho, e desmarcamos a posição.
- Caso contrário, vamos chamar as 4 correspondentes as 4 direções, se elas forem válidas, ou seja, se estão dentro do grid e não foram visitadas no caminho atual, já que ele deve ser simples:
- Se está dentro do grid e , então chamamos .
- Se está dentro do grid e , então chamamos .
- Se está dentro do grid e , então chamamos .
- Se está dentro do grid e , então chamamos .
- Por fim, marcamos como não visitado: .
Clique aqui para conferir o código
Clique aqui para conferir a solução em vídeo feita pelo NepsAcademy
Wifi
Comentário escrito por Enzo Dantas
Conhecimento prévio necessário:
Observações e ideia
A primeira observação do problema é a seguinte: não faz sentido colocar uma antena em um quarto em que há outro quarto dentro. No exemplo abaixo, por exemplo, é fácil ver que é melhor colocar a antena no lugar circulado em vermelho do que o lugar atual da antena, pois ele vai pegar todo o espaço pego atualmente mais o quarto em branco. Sendo assim, todas as antenas a serem colocadas serão colocadas em quartos que não contém outro quarto (vamos chamar esses quartos de "vazios"). Além disso, perceba que todos os quartos vazios precisarão de uma antena, pois o sinal não vai chegar de uma antena de fora. Assim, a resposta é a quantidade de quartos vazios.
Como checar se um quarto é vazio:
Para um quarto ser vazio, não deve ter nenhum retângulo dentro dele. Vamos representar os retângulos como o ponto superior direito deles (ponto do retângulo A dentro do retângulo B <=> A dentro de B).
Para checar se um quarto Q é ou não vazio, vamos contar quantos quartos estão dentro de Q, ou seja, quantos pontos estão dentro de Q. Isso pode ser feito do seguinte modo:
A quantidade de pontos no retângulo circulado em vermelho é a quantidade de pontos em (, , e são os prefixos 2d representados pelos 4 pontos do retângulo, direito-superior, esquerdo-superior, direito-inferior, esquerdo-inferior).
Como calcular quantos pontos estão em um prefixo 2d:
Esse problema é relativamente conhecido. Ordene os pontos em ordem crescente de X. Quando estamos no ponto , todos os pontos considerados estão antes de na coordenada X. Assim, todos os pontos já considerados que possuem uma coordenada Y menor que a coordenada Y do ponto fazem parte do prefixo de . Inicialize um array com INF. Quando estivermos percorrendo o array, primeiramente vamos passar pelos pontos e , e vamos salvar o valor de em . Depois disso vamos passar pelos pontos e e, se , então achamos um quarto vazio. Sabemos se estamos olhando para A/D ou para C/B baseado em (se , estamos olhando para A/D, e para C/B c.c.).
Implementação da atualização e query do prefixo 2d:
Vamos manter uma BIT de soma. Quando acabamos de considerar um retângulo cujo ponto superior-direito é o ponto , adicionamos o Y desse ponto () na BIT. Ou seja, fazemos a operação . Para saber quantos pontos possuem um Y menor ou igual ao do ponto atual, fazemos a query da BIT. Entretanto, as coordenadas dos pontos podem ir até . Como só queremos saber quais pontos vêm antes de quais (a ordem relativa) e as coordenadas podem ir até , faremos uma compressão de coordenadas. Vamos criar um array , onde salvamos todas as coordenadas Y. Vamos ordenar esse array e excluir repetidos. Agora, em vez de fazer e , fazemos e , onde representa a posição de no array . é achado usando uma busca binária em .
Complexidade:
Para ordenar os pontos a complexidade é . Além disso, vamos fazer operações de update na BIT () e buscas binárias em um array de tamanho (). Logo, a complexidade final é .
Clique aqui para conferir o código
Clique aqui para conferir a solução em vídeo feita pelo NepsAcademy
Elevador
Comentário escrito por Vitor Veiga
Conhecimento prévio necessário:
No problema, nos é dado pesos referentes a caixas que devemos levar do primeiro ao segundo andar utilizando um elevador e queremos saber se é possível levar todas elas. Nesse elevador a diferença máxima entre os pesos dos dois compartimentos deve ser de 8 unidades, e quando um compartimento sobe o outro desce.
Quando é possível levar todos os pesos, a estratégia mais segura para levá-los é a seguinte:
Se temos pesos , ordenados do menor até o maior, escolhemos levar sempre primeiro o mais pesado para cima, no caso o . Para levá-lo, primeiro subimos o e descemos nada, depois subimos o e descemos o , e trocamos o pelo . Subimos o e descemos o e trocamos o por para finalmente subir o e deixá-lo lá. Agora que o mais pesado já está em cima, sobram 3 pesos e é só repetirmos o processo para o peso , seguido do , e depois .
Essa estratégia é ótima, e só não funciona quando a diferença de peso entre dois pesos adjacentes, depois de ordenados, é maior que 8 unidades, pois nunca conseguiríamos subir um utilizando outro com 8 unidades a menos que ele, travando a estratégia.
Portanto, para sabermos se é possível ou não levar todos os pesos para o segundo andar, basta checar se a diferença entre quaisquer pesos adjacentes é maior que 8. Se for, não é possível levar os pesos. É importante lembrar também que o peso mais leve deve ser menor ou igual a 8, pois, caso contrário, o elevador não conseguiria levantá-lo sozinho.
No segundo caso teste, temos:
, que ordenando fica:
, onde temos, entre e , uma diferença maior que 8, ou seja, não é possível levar todas as caixas.
Clique aqui para conferir o código
Clique aqui para conferir a solução em vídeo feita pelo NepsAcademy