Seletiva IOI 2018 - Dia 4
Se você quiser se preparar para a OBI e seletiva, não deixe de conferir o Roteiro de estudos de informática, e também a página de informática do NOIC, com todos nossos materiais.
Clique aqui para conferir a prova na íntegra *A seletiva da OBI 2017 foi feita para a IOI 2018
Jogo
Solução escrita por Rafael Nascimento
Conhecimento prévio necessário:
Nesse problema, nós podemos desenhar um segmento de reta entre dois pontos quaisquer, e a pontuação consiste na soma de todos os pontos que interceptam com o segmento de reta.
Para resolver este problema, usaremos uma propriedade geométrica:
Digamos que tenhamos uma linha e escolhemos dois pontos quaisquer contidos nelas. Podemos afirmar que o ângulo alfa (como pode ser visto na imagem acima) sempre será o mesmo independentemente de quais pontos contidos na linha sejam escolhidos. Essa propriedade gera uma forma fácil de saber se o ponto está contido na linha.
Dessa forma, imagine que nós temos o seguinte algoritmo:
- Nós escolhemos com força bruta o primeiro ponto (ponto A) a pendurar a corda.
- Nós escolhemos com força bruta o segundo ponto (ponto B) a pendurar o final da corda.
- Nós iteramos por todos os pontos (ponto i) e checamos se o ponto está contido na corda, ou seja, comparamos os alfas.
Dessa forma, nós temos um algoritmo que é capaz de resolver a segunda parcial, obtendo 25 pontos.
Agora nós temos que otimizar esse algoritmo de força bruta para ser capaz de computar a resposta em aproximadamente .
Primeiro, vamos sortar os pontos pela coordenada . Assim, afirmarei que o ponto A é o mais a esquerda, e o ponto B o mais a direita (considerando a coordenada
).
A ideia é que nós não precisamos iterar por todos os pontos no #3, porque nós já iteramos todos os pontos contidos no segmento entre A e B enquanto executávamos a etapa #2. Isso é verdade porque todos os pontos que estão no segmento de reta entre A e B possuem sua coordenada entre a coordenada
e a coordenada
, e já que estamos iterando em ordem (nós sortamos as coordenadas), nós iteramos todos os pontos entre
e
.
Dessa forma, quando iteramos os pontos pela etapa #2, podemos salvar os ângulos dos pontos anteriores em uma estrutura como map ou unordered_map. Assim, ao invés de consultar todos os pontos na etapa #3, podemos simplesmente checar o map, consultando a soma dos pontos que possuem aquele ângulo.
O código abaixo possui complexidade , já que foi utilizado map. No código, foi armazenado o ângulo através de duas informações: O quadrante (considerando o ponto A como o centro
do quadrante) e o seno.
Clique aqui para conferir o código
Chuva
Comentário por Fernando Gonçalves
Conhecimento prévio necessário:
Nesse problema, queremos saber em quantos coletores de água um retângulo de chuva chega, sendo que a aǵua sempre vai para uma casa adjacente que tem altura menor ou igual a sua atual.
A principal observação desse problema é que devemos encarar ele ao contrário do que ele é apresentado: ao invés de calcular para cada retângulo quantos coletores ele afeta, devemos calcular para cada coletor de quais retângulos ele recebe água.

Para tanto, devemos calcular para cada coletor quais posições levariam água a ele. Podemos fazer isso por meio de uma dfs ou uma bfs, que começa na posição do coletor e passa pela matriz, caminhando somente para posições que tenham altura maior ou igual a sua. Também marcamos todas as casas pelas quais passamos, desta forma as casas que chegam no coletor estarão marcadas com , enquanto aquelas que não chegam estarão marcadas com
.
Agora, só precisamos checar, para cada retângulo, se a sua água chega no coletor, ou seja, se dentro dele existe alguma casa marcada com . Podemos fazer isso rapidamente usando somas de prefixo 2D, pois haverá alguma casa marcada com
dentro do retângulo se, e somente se, a soma da marcação das casas for maior que
.
Em suma, nosso algoritmo fará o seguinte:
- Para cada coletor, uma busca no grafo que marca aqueles em que o coletor chega;
- Tendo marcado as casas, pré-calcular as somas de prefixo 2D da matriz de marcação;
- Passar por cada retângulo checando se ele chega no coletor;
- Por fim, imprimir a resposta.

As complexidades serão:
para as buscas
para o pré-calculo das somas de prefixo 2D
para checar os retângulos
no total
Obs: o tempo limite desse problema está bem apertado, podem ser necessárias otimizações de constante ou de cache. Em caso de dúvida, cheque o código.
Clique aqui para conferir o código
Danone
Comentário escrito por Arthur Lobo
Conhecimento prévio necessário:
A ideia inicial do problema vai ser pensar na soma no formato de prefix sum, sendo . Se sabemos que a soma de
até
é
, então
. Isso quer dizer que sabemos qual diferença entre os valores de
e
. Conseguir responder uma query de um
até um
significa saber a diferença entre os valores de
e
.
Agora vamos ver o que podemos fazer quando temos várias informações de soma:
Antes de proseguir, pense em todos os intervalos que conseguimos saber a soma.
Resposta: conseguimos saber a soma de todos os intervalos em que o início é um dos , o final é um dos
e o início é menor que o final, ou seja,
.
O motivo disso é que conseguimos saber o valores de em função de
:
Portanto, se queremos saber, por exemplo, a soma de até
, basta fazer
.
A ideia vai ser manter vários valores de em relação à um outro
. Vamos manter dois vetores:
Em relação à qual índice você está mantendo
. Inicialmente,
.
Qual a diferença entre
e
. Ou seja,
. Incialmente
.
Subtask 1 (
)
Primeiro, vamos pensar em como lidar com uma Query perguntando a soma de até
. Se
, então é impossível saber a resposta. Caso contrário, a resposta é
, pois
desaparece na subtração.
Agora basta lidar com um update falando que a soma de até
é
. Se
, então já sabemos essa informação e podemos ignora-la. Caso contrário, temos que juntar as "componentes" de
e
. Para isso, vamos fazer todo mundo que tem
ter
, mas para isso temos que mudar o valor de
:
Para todo com
, temos
, portanto, se quisermos mudar
para ser
, então precisamos mudar
para
.
Conseguimos dar um merge nas duas componentes dessa maneira. Perceba que é possível visualizar o problema como um grafo em que ligamos uma aresta entre e
se sabemos a soma entre eles 2 através de um update e as componentes desse grafo são os pares que sabemos a soma.
Esse código fica para responder a query e
no pior dos casos para lidar com um update, portanto fica
.
Subtask 2 (
)
Precisamos diminuir a complexidade do update, para isso basta usar um small-to-large na hora do merge das componentes. Se a quantidade de com
for menor que a quantidade com
, então fazemos os
t.q.
virarem
. Senão, fazemos o contrário.
Com isso, o update fica, no total, e código inteiro fica
.
Clique aqui para conferir o código