Seletiva IOI 2020 - 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 2019 foi feita para a IOI 2020
Torneio
Escrito por Caique Paiva
Conhecimento Prévio Necessário:
- Problemas Interativos
- Grafos
Esse problema foi um pouco diferente de outros problemas da seletiva. Ele é um problema interativo de grafos, e todas as subtasks são disjuntas do problema final, então, vamos resolver cada subtask de cada vez. Uma coisa que vai ser útil em cada subtask é ver o problema como um grafo direcionado completo, onde se, e somente se, ganha de . Para todas as subtasks, defina query como a interação de vencedor_partida.
Subtask 2:
Conhecimentos Prévios Necessários:
Veja que, se olharmos na linguagem de grafos, o problema pergunta qual que é o grau de saída de todos os vértices. Portanto, perceba que o grau de saída de todos os vértices são diferentes, pois caso exista dois vértices e tal que , suponha sem perda de generalidade que ganha de , portanto todo mundo que ganha, também vai ganhar, logo , absurdo!
Com isso, como , temos que é uma permutação de , então, se ordenamos os vértices por grau de saída, vamos saber o valor de cada um deles! Então o problema vira ordenar os vértices por grau de saída. Mas isso é simples, pois se ganha de , então , logo, se ganha de , então está numa posição maior que o na ordenação, podemos fazer um mergesort para calcular isso em , que passa na quantidade de queries!
Subtask 3 e 4:
Veja que, caso ganhe de , então não pode ser resposta. Logo, vamos fazer o seguinte algoritmo. Primeiro, faça .
- Caso tamanho de seja , pare o algoritmo. Caso contrário, seja os valores do conjunto . Faça , ou seja, fazendo as queries em pares até o algoritmo parar, e tire do conjunto quem perdeu, ou seja, estamos mantendo somente as possíveis respostas no conjunto.
Seja o elemento que sobrou no conjunto. Agora, faça as perguntas . Caso realmente tenha ganhado de todo mundo, imprima como resposta, caso contrario, imprima que não tem. Veja que cada passo do algoritmo gasta tamanho de dividido por 2 queries, e diminui o tamanho do conjunto pela metade, e como o conjunto começa com elementos, a quantidade de queries que esse algoritmo gasta é , onde . além disso, o segundo passo do algoritmo gasta queries, portanto, o algoritmo todo roda em , que passa na quantidade de queries.
Subtask 5:
Vamos provar o seguinte lema:
Lema: Em um grupo de pessoas, existe alguém que perdeu mais que 4 partidas
Prova: Suponha por absurdo, que no grupo de pessoas, todos perderam no máximo partidas. Traduzindo para a linguagem de grafos, temos que para todo vértice, e portanto, , pois a quantidade de partidas para cada vértice é . Portanto, sabemos que
Absurdo! Logo, em um grupo de pessoas, existe alguém que perdeu.
Dado esse lema, vamos fazer o seguinte algoritmo. Seja o conjunto das nossas possíveis respostas. Comece com , e crie um vetor chamado , que guarda em qual é o do vértice .
- Passo : Vamos ver se é uma possível resposta ou não. Seja os elementos de . Faça as perguntas , e para quem perder em cada partida, aumente o desse vértice em . Bote em , e, para todo vértice em , caso , tire o do conjunto. Pare o algoritmo quando .
Por conta do lema, temos que o tamanho de sempre é menor ou igual que , portanto, fazemos no máximo queries, pois, em cada passo, fazemos queries, onde é o tamanho do conjunto , e . Veja que, para todo elemento fora de , , logo ele não é resposta, então, basta testar para os elementos em para ver se eles são respostas, logo, para cada elemento de , faça , e caso , tire ele do conjunto. Nesse passo, fizemos no máximo queries, pois temos no máximo elementos em . Daí, seja a resposta do nosso problema. No total, fizemos no máximo queries, que passa no nosso problema.
Subtask 6:
Lema: Caso um torneio tenha um ciclo, então ele tem um triângulo (Ou seja, um cíclo de tamanho 3).
Prova: Suponha que o nosso grafo não tem ciclo de tamanho . Seja o tamanho do menor ciclo do grafo. Seja os vértices desse ciclo. Veja os vértices . Se ganhar de , então nós achamos o nosso triângulo! Caso contrário, ou seja, ganhe de , então, também é ciclo, e tem tamanho , absurdo, pois é o ciclo de menor tamanho (Veja que esse argumento não funcionaria para pois já estamos usando a aresta no ciclo).
Logo, ao invés de acharmos um ciclo qualquer, podemos ir atrás de um triângulo! Primeiro, vamos retirar todos os vértices com . Podemos descobrir quais são esses vértces fazendo a pergunta de quantidade de vitórias para cada vértice . Depois de feito isso, pegue o vértice com menor , chame-o de , e divida o grafo em grupos, os que vencem de e os que perdem de . Para descobrir esses vértices, basta fazer para todo . Veja que, como nós retiramos os vértices com do grafo, então nem o grupo de vencedores e nem o grupo de perdedores estão vazios.
Lema 2: Alguém que perde de vai ganhar de alguém que ganha de .
Prova: Suponha por absurdo que todo mundo que perde de também perde de todo mundo que ganha de . Seja o vértice de máximo do grupo de perdedores. Veja que, como perde para todo mundo que ganha de , então ele só pode ganhar de quem perde para , ou seja, , absurdo, pois escolhemos para ser o vértice com mínimo.
Portanto, seja o vértice de máximo no grupo dos perdedores. Como provado acima, ele vai ganhar de alguém do grupo dos vencedores, logo, basta fazer para todo vértice no grupo dos vencedores, até achar alguém que perde para . Feito isso, nós vamos ter achado nosso triângulo, e o problema acaba fazendo menos de queries, que passa nas constantes do problema
Ratinhos
Escrito por Caique Paiva
Subtask 2 ():
Conhecimentos Prévios Necessários:
Vamos dar uma olhada num exemplo qualquer.
Temos caixas, as caixas . Temos também um queijo em , e outro em . Podemos ver então, que a resposta desse caso é , pois podemos começar com o ratinho em , quebrar as caixas , e , e então ir para a casinha . Veja como nós construímos essa resposta, começamos em um queijo, formos quebrando as caixinhas para "cima", até conseguir ter um caminho para chegar na casinha do outro queijo, quebrando umas casas para "baixo". Os termos "cima" e "baixo" vem de acordo com a profundidade das caixas. Ué, cima, baixo, profundidade? A maneira que a gente construiu esse caminho lembra muito como a gente constrói caminhos em árvores!
Então a ideia vai ser o seguinte, vamos criar uma árvore de vértices, onde a raiz dessa árvore vai ser o vértice , e vamos criar uma caixa imaginária gigantesca, que contém todas as outras caixas. Então, para cada , o pai dele vai ser , onde, se eu furar a caixa , eu vou direto para a caixa . A definição formal é bem feia, mas basta ver o exemplo para as caixas acima que vai ficar bem mais claro:
Vamos chamar aquela caixa que encobre todas as outras de . Então, se eu sair da caixa (A caixa ), aonde eu vou parar? Para a caixa , que é o pai dela. Se eu furar a caixa , para qual caixa eu vou? Para a caixa . E assim vai. E com essa interpretação, o problema ficou muito simpes.
Temos uma árvore de vértices, e queries. Na query , recebemos vértices. Sua tarefa é achar o menor caminho que passa por todos os vértices selecionados.
Vamos ver como resolver esse problema na próxima subtask, mas, para resolver a subtask de , basta pegar os dois vértices, e calcular a distância deles na árvore, e podemos fazer isso com algoritmo de LCA.
Subtask 4:
Conhecimentos Prévios Necessários:
Para essa subtask, vou explicar o conceito de Árvores Virtuais. Caso queria saber mais sobre, veja esse vídeo no youtube. Imagine que temos uma query com os vértices , então, queremos achar o menor caminho que passa por todos esses vértices. Veja que qualquer caminho que passa por todos esses vértices, também vai ter que passar pelo LCA dois a dois de todos os vértices (Ou seja, o caminho passa pelo LCA(), LCA(), LCA(), e assim vai). Então, vamos definir uma árvore virtual:
Seja uma árvore e vértices escolhidos em . A árvore virtual desses vértices vai conter e todos os LCA dois a dois desses vértices. Além disso, as arestas desse grafo tem pesos, e a na árvore virtual é a mesma que a distância de na árvore original.
Certo, como construir essa árvore? Para isso, vamos pegar o tempo de entrada da dfs começando por de todo vértice, ordene pelo tin, e então, pegue , coloque eles num vetor junto com , ordene por tin de novo, e então, teremos vértice , e então, adicione a aresta com peso .
Esse algortimo constrói a árvore que queriamos. Basta ver que, se eu tenho um par , , que então vai ser o LCA de algum dois vértices consecutivos desse intervalo (De novo, caso queria mais detalhes sobre porque isso funciona, veja o vídeo acima).
Perfeito! Construimos nossa árvore virtual em , onde é a quantidade de vértices da query, e então, reduzimos a query para: Dado uma árvore, qual é o menor caminho que passa por todos os vértices dela? E agora para resolver isso é simples. Como eu vou passar por todos os vértices da árvore, eu vou passar por todas as folhas, então, o que eu posso fazer é: Escolher duas folhas , pegar o caminho entre elas, e então, para cara vértice no caminho de até , eu pego todos os vértices da subárvore dele que não estão no caminho, passo por eles e volto para esse vértice, e então prossigo no caminho. Esse algoritmo vai percorrer por todos os vértices, e tem tamanho soma dos pesos de todas as arestas do grafo . Portanto, o melhor par que podemos escolher, é o par com máximo! Ou seja, basta rodarmos um algoritmo de achar o diamêtro na árvore virtual, que nós temos a resposta da query, e o algoritmo de diâmetro é !
Complexidade final: , que passa, pois .