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
.
- Se
- 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