Escrito por Lúcio Figueiredo, Leonardo Paes e Thiago Mota
Para conferir a prova na íntegra, clique aqui.
Metrô da Nlogônia
Conhecimento prévio necessário:
Note que os sistemas Círculo e Quadrado são ambos árvores, ou seja, grafos conexos e acíclicos. Assim, a tarefa do problema é encontrar dois vértices e de modo que pertença ao sistema Círculo, pertença ao sistema Quadrado e o diâmetro do grafo resultante ao ligar e (que será uma árvore) sejá o menor possível.
Para uma árvore qualquer, defina (ou excentricidade de em ) como a maior distância do vértice para algum vértice de , ou seja, a maior profundidade de algum vértice quando a árvore é enraizada no vértice . Sendo a árvore do sistema Círculo e a árvore do sistema quadrado, o seguinte lema vale:
Lema 1: Se é um vértice de e é um vértice de , o diâmetro da árvore resultante ao ligar e será igual a , onde é o diâmetro de uma árvore qualquer.
Na figura acima, obtemos que o diâmetro da árvore resultante ao ligar e é , já que , e .
Como o valor de e não dependem da nossa escolha de e , queremos escolhê-los de forma que o valor de seja o menor possível, ou seja, de modo que e tenham a menor excentricidade possível nas suas respectivas árvores.
Para uma árvore qualquer, o vértice cuja excentricidade é a menor possível é chamado centro da árvore. Logo, o problema se resume em encontrar os centros de cada uma das duas árvores. Para entender como achar o centro de uma árvore, acesse o material do Noic sobre Diâmetro de uma Árvore.
Confira o código abaixo para melhor entendimento:
Mesa Redonda
Conhecimento prévio necessário:
Primeiro temos que descobrir em qual posição Ana irá sentar. Para descobrir isto, basta checar o resto da divisão do número sorteado por . Com essa informação, iremos utilizar o mesmo procedimento para descobrir a posição de Ana, com a diferença de que se a posição que Beatriz for sentar já foi utilizada por Ana, ela terá que ser movida uma cadeira para frente. Desse modo, resta olhar qual cadeira sobrou e essa posição é a resposta.
Exploração do Capitão Levi
Conhecimento prévio necessário:
Inicialmente, vamos ordenar os pontos dados de maneira crescente em relação à coordenada , ou seja, de modo que para qualquer par de pontos com índices e com , vale que .
A tarefa do problema se resume em calcular a quantidade de pares de pontos de forma que . Como o par é equivalente ao par , vamos calcular os pares de pontos de modo que , para garantirmos que possuirá um valor positivo (devido à ordenação inicial).
Para simplificar o problema, caso seja negativo, vamos multiplicar o valor de e de por ; desta forma, tornamos positivo e não modificamos o valor de . Feito isto, podemos trabalhar de maneira simples com a inequação principal do problema, da seguinte forma:
.
Sendo , utilizando a inequação , reduzimos o problema para calcular, para cada ponto , a quantidade de pontos de modo que .
Podemos resolver este problema utilizando um vetor de frequências: Vamos iterar pelos pontos desde até , e armazenaremos em a quantidade de pontos com índice maior que cujo valor é igual a . Assim, no ponto , aumentamos nossa resposta em , e após isso, adicionamos no valor de .
No entanto, note que a complexidade do método descrito não é suficiente para resolver o problema, já que o valor máximo de pode ser grande. Porém, perceba que podemos otimizar o nosso algoritmo utilizando uma BIT: A operação de calcular é idêntica à função da BIT e a operação de adicionar a é equivalente a .
Agora, só resta uma dificuldade em nossa solução: O valor de pode ser grande (até ), e o tamanho do vetor da BIT deve ser grande o suficiente para armazenar valores em posições desta magnitude. Porém, perceba que existem, no máximo, valores distintos de . Logo, podemos utilizar a técnica de Compressão de Coordenadas nos valores , e assim, utilizar a BIT com memória de tamanho .
Assim, a complexidade final da solução é , já que precisamos ordenar o nosso vetor e utilizar as funções da BIT.
Confira o código abaixo para melhor entendimento:
Grand Prix da Nlogônia
Conhecimento prévio necessário:
Inicialmente, perceba que podemos realizar uma busca binária para encontrar o menor índice tal que ao aceitar cada plano de a , o grafo resultante possuirá um ciclo. Porém, precisamos construir o grafo de maneira eficiente, já que cada plano pode adicionar até novas arestas no grafo.
Construindo o grafo modificado
Note que cada plano adiciona arestas no grafo de uma forma específica: um plano liga ao intervalo de vértices . Tendo em mente que estamos trabalhando com intervalos, vamos utilizar uma Árvore de Segmentos que nos auxiliará na construção do grafo.
A segment tree será utilizada como um grafo, em conjunto com os vértices iniciais. A construção deste novo grafo será realizada da seguinte forma:
- Cada vértice não-folha da segment tree terá uma aresta direcionada para os seus dois filhos, de modo que partindo de um vértice que representa um intervalo na árvore, conseguimos chegar em cada folha neste intervalo.
- A folha de intervalo na segment tree possuirá uma aresta direcionada para o vértice do grafo inicial.
- Ao adicionarmos um plano , iremos criar arestas direcionadas do vértice para cada nó da segment tree que está totalmente contido no intervalo (ou seja, os mesmos nós utilizados ao fazer uma query no intervalo da segment tree, quando a utilizamos em sua aplicação convencional).
Perceba que, ao adicionarmos as arestas desta forma, um vértice que foi ligado ao intervalo consegue alcançar todos vértice : Basta seguir a aresta de ao vértice da segment tree que "cobre" o vértice (ou seja, o nó da árvore cujo intervalo contém ), percorrer o caminho deste vértice até a folha da árvore que representa e, por fim, percorrer a aresta desta folha para .
Logo, se consegue alcançar o vértice no grafo original (o grafo resultante ao criar arestas do vértice de um plano a todos os vértices no intervalo neste plano), também consegue alcançar no grafo modificado com a segment tree. Além disso, como o intervalo é representado pela união de nós da segment tree, a quantidade de arestas criadas ao aceitarmos planos é de complexidade .
Na imagem acima, vértices azuis representam os nós da árvore de segmentos, e os vértices de cor rosa representam os vértices originais. A imagem indica as arestas presentes no grafo para e com os planos e .
Encontrando um ciclo no novo grafo
O único problema que ainda precisamos resolver é como descobrir se o grafo resultante ao adicionar um prefixo de planos (durante a busca binária) é acíclico ou não. Podemos descobrir isso utilizando uma DFS e um vetor de marcação, digamos, . O vetor de marcação possuirá os seguintes valores:
- Se o vértice ainda não foi percorrido pela DFS, .
- Se o vértice foi percorrido pela DFS, mas a sua DFS ainda não foi concluída, .
- Se a DFS de foi concluída, .
Note que um ciclo será encontrado se, e somente se, durante a DFS realizada em algum vértice , existe um vértice uma aresta de para algum vértice de modo que , pois neste caso, existe um caminho de para que não percorre a aresta . Assim, conseguimos encontrar um ciclo no grafo com complexidade (complexidade da DFS).
Complexidade final
Durante cada uma das iterações da busca binária, serão construídas arestas no grafo, onde é a quantidade de planos aceitos; assim, a DFS realizada possuirá complexidade , e portanto, a complexidade final da solução é .
Confira o código abaixo para melhor entendimento:
Etiquetas
Conhecimento prévio necessário:
Para resolvermos esse problema, utilizaremos uma técnica conhecida como Programação Dinâmica. A ideia será calcular a soma dos valores dos inteiros que iremos tirar ao usar as etiquetas, com o objetivo de minimizá-la, já que queremos maximizar a soma dos números que restaram.
Seja o mínimo valor possível considerando apenas a fita formada pelos primeiros inteiros e utilizando etiquetas de tamanho . Então, as transições da programação dinâmica consistem de duas escolhas:
- Colocar uma etiqueta na posição atual (), cobrindo os números de índice até . O valor deste caso é igual a , onde é igual à soma dos inteiros no intervalo . Podemos encontrar esta soma em utilizando a técnica de soma de prefixos.
- Não colocar uma etiquta na posição . O valor deste caso é igual a .
Logo, a resposta para o problema será a soma de todos os inteiros do vetor dado subtraída de . A complexidade final da solução é , já que possui estados de complexidade e , com transições em .
Abaixo se encontram duas maneiras diferentes de se implementar a programação dinâmica; a primeira versão calcula recursivamente, e a segunda, iterativamente.
Os casos bases para na solução recursiva são: e , .
Os casos bases para na solução iterativa são: e , .
Confira os códigos abaixo para melhor entendimento:
Solução Recursiva:
Solução Iterativa: