OBI 2023 - Fase 2 - Turno B - Programação Nível 2
Se você quiser se preparar para a OBI, 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 os nossos materiais.
Para conferir a prova na integra, entre no site da OBI.
Brincadeiras de Roda
Comentário escrito por Enzo Dantas
Conhecimento prévio necessário:
Imagine um círculo com 5 pessoas. Se o professor bate palma 5 vezes, percebemos que todas as pessoas dão uma volta completa e continuam no mesmo lugar, e percebemos que isso também vale para qualquer número múltiplo de 5. Sendo assim, em um círculo com pessoas, vamos nos importar apenas com a quantidade de palmas módulo . É fácil ver que uma pessoa qualquer que começa na posição vai terminar aproximadamente na posição . Trabalhando com casos pequenos chegamos na fórmula exata , e essa é a solução em .
Além disso, os limites pequenos do problema permitem uma solução ainda mais direta: simular cada palma do professor. Se a posição atual é igual a , então a posição depois da palma é 1. Caso contrário apenas adicionamos 1 à posição. Complexidade: O(P).
Clique aqui para conferir o código (Solução 1)
Clique aqui para conferir o código (Solução 2)
Startup
Comentário escrito por João Pedro Castro
Conhecimento prévio necessário:
Podemos visualizar a hierarquia dessa empresa como um grafo, onde os vértices (funcionários) tem outros vértices se ligando à ele (os subordinados). Além da estrutura do grafo, para cada funcionário vamos guardar seu chefe, salário, e a quantidade de subordinados com os quais ele está insatisfeito. Também é necessário guardar a quantidade de funcionários insatisfeitos no total, chamaremos esse valor de .
Após montar o grafo (feito com as arestas sendo: ), receber o chefe de cada um e o salário, para preencher a quantidade de insatisfações basta percorrer o grafo começando do vértice 1 e aumentando o valor de em uma unidade toda vez que , quando isso acontecer, também aumente em 1 caso , já que o estado do vai mudar de satisfeito para insatisfeito.
Para todo funcionário atualizado a ideia é checar se o "estado" (satisfeito ou insatisfeito) de seu chefe e dele mesmo (em relação ao seus subordinados) foram alterados, já que eles são os únicos com os quais isso pode ter acontecido. E isso pode ser feito com uma série de estruturas condicionais, que se resumem à:
- (chefe insatisfeito antes com o funcionário) e (chefe satisfeito agora)
- (chefe satisfeito antes com o funcionário) e (chefe insatisfeito agora)
Agora, para achar o valor de (a resposta) atualizado, é só seguir o seguinte algoritimo:
- Se
- Se (novo chefe insatisfeito):
- Se não se:
- Se (chefe satisfeito de novo):
- Para cada subordinado de :
- Se :
- Se (novo chefe insatisfeito):
- Se não se: :
- Se (chefe satisfeito de novo):
- Se :
Assim você também garante que os valores de e sempre permanecem atualizados. Segue a solução em C++:
Corridas
Escrito por Caique Paiva
Conhecimentos Prévios Necessários:
- Básico de Geometria
A principal ideia desse problema é que a solução vai ser um retângulo. Para perceber isso, basta ver que, se temos uma solução ótima, podemos ir "dobrando" a cerca para que ela vire um retângulo. Vamos ver um exemplo para ficar mais claro:
Na primeira figura, temos um caminho que é solução do problema, e então, a ideia é pegar essas pontas que não são um retângulo, e dobrar elas para se tornar um retângulo, e isso não muda o tamanho da cerca, logo podemos fazer isso. Então, fazemos isso até que a figura não tem mais nenhuma ponta.
Agora, dado que a figura é um retângulo, qual é o retângulo que é solução? É o retângulo que abrange a figura toda, ou seja, se olharmos os pontos como coordenadas da forma , precisamos que o ponto com o menor do caminho esteja no retângulo, o com maior também esteja, o com menor também esteja, e o com maior também esteja, e veja que essa condição também é suficiente para construir o retângulo. Dito isso, seja o maior x, menor x, maior y e menor y, então, o perímetro desse retângulo .
Com todas essas conclusões, como vamos fazer nossa resposta? Vamos simular o caminho feito no problema (Se estamos no ponto e temos que subir casas, fazemos , e análogos) e vamos salvando qual é o maior x, o menor x, o maior y e o menor y, e no final, calcular a resposta com o que foi dito no paragrafo passado.
Fortunas
Comentário por Murilo Maeda e Henrique Vianna
Conhecimentos Prévios
Esse é um problema relativamente complicado e descobrir a ideia final requer várias observações. Por isso, vamos comentar todas as parciais do problema, já que elas ajudam a resolver para 100 pontos.
Subtarefa 1:
Já que , haverá uma única reunião. Então, podemos simplesmente fazer uma a partir da anfitriã e ir marcando quem estiver dentro do intervalo estipulado.
Subtarefa 2: para todo – ou seja, a mãe da matriarca é a matriarca
O que essa subtarefa quer dizer é que a árvore vai ser uma linha, ou seja, se tomarmos a matriarca 1 como raíz, todos os vértices (fora a única folha) vão ter exatamente 1 filho. Com isso temos basicamente um vetor no qual os valores das fortunas vão decrescendo conforme o índice aumenta.
Para resolver esse problema, vamos manter um vetor auxiliar de tamanho que guarde no índice para quantas reuniões a matriarca foi convocada. Também vamos representar a árvore como um vetor que guarde na posição a fortuna da matriarca . Outra observação importante é que uma convocação agora se resume a encontrar a matriarca de menor índice cuja fortuna seja menor ou igual a e a matriarca de maior índice cuja fortuna seja maior do que .
Então, como o vetor está ordenado (do maior para o menor), basta fazermos duas buscas binárias: uma para encontrar a matriarca de maior fortuna que foi convocada e outra para encontrara a matriarca de menor fortuna que foi convocada e depois adicionar 1 no vetor de todo mundo entre essas duas (incluindo elas).
Para adicionar 1 em todos os elementos do vetor , vamos usar um truque de somas parciais. Para esse truque, se quisermos adicionar 1 nos elementos do intervalo , basta irmos no vetor , que guarda as somas parciais de prefixo do vetor , e somarmos 1 no índice e -1 no índice . dessa forma, somente os elementos no intervalo que queremos serão afetados quando calcularmos a soma daquele intervalo usando somas parciais.
Para a resposta, basta pegar a soma parcial de cada matriarca individualmente.
Subtarefa 3: e
Ao que essa tarefa se resume é que todo mundo na subárvore da anfitriã vai ser vai ser chamada. Ou seja, para cada das reuniões que forem feitas, basta somar 1 no vetor de resposta de todo mundo que está na subárvore da anfitriã. Para fazer isso, podemos linearizar a árvore e utilizar o mesmo truque de somas parciais que usamos na parcial anterior.
Para linearlizar a árvore, podemos utlizar a técnica "Euler Tour". Para melhor compreensão, analise o código a seguir:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
int timer, tin[MAXN], tout[MAXN]; | |
void dfs(int u, int p) | |
{ | |
tin[u] = ++timer; | |
for(auto v : adj[u]) | |
{ | |
if(v == p) continue; | |
dfs(v, u); | |
} | |
tout[u] = timer; | |
} | |
Na implementação acima, temos: guarda o "tempo" atual da , guarda o tempo em que se "entra" no vértice , ou seja, o valor de quando foi chamada e , que representa o tempo em que se "sai" do vértice , ou seja, o maior valor de para algum na subárvore de . Sendo assim, a subárvore de qualquer vértice pode ser representada por um intervalo continuo , daí o nome "linearização".
Subtarefa 4: e
Note que nessa tarefa ainda mantemos a característica da subtarefa anterior de que quando uma reunião acontece, somente a subárvore da anfitriã é convidada. Mas, agora, a característica que não está presente é a de que todos vão ser chamados. Como isso acontece, não podemos mais utilizar o truque de somar 1 em todos os elementos do intervalo, já que podem haver "buracos" nos segmentos.
O que a outra condição que o enunciado nos dá, o limitante de fortuna, garante é que a altura máxima da árvore vai ser 200, já que há uma diferença de pelo menos 1 da fortuna de uma matriarca mais velha para outra mais nova. Então, podemos simplesmente marcar cada uma das anfitriãs e qual é o intervalo da reunião que elas fizeram. Para calcular a resposta pegamos um vértice por vez e subimos até a raiz, calculando para cada um dos vértices no caminho em quantas festas que ele foi anfitrião o vértice do qual nós partimos participou. Mas, note que, uma única matriarca pode ser anfitriã de várias festas com vários intervalos diferentes, então temos que encontrar uma maneira de guardar e checar a quais intervalos uma determinada fortuna pertence quando estamos subindo pela árvore.
Para fazer isso podemos simplesmente guardar um vetor de marcação para cada um dos vértices que indica para cada fortuna quantas festas ela participou com aquele vértice de anfitrião. Isto é, se um vértice é anfitrião de uma festa de intervalo e , basta somarmos 1 no intervalo do vetor de marcação do vértice . Quando formos calcular a resposta para um vértice de fortuna e estivermos passando pelo vértice , somamos o número guardado no vetor de marcação do vértice na resposta do vértice .
Subtarefa 5: Sem restrições adicionais
Para resolver o problema completamente deve-se perceber que é válido simplesmente “transferir” uma reunião de um vértice para o seu último ancestral que tem fortuna dentro do intervalo, ou seja, o ancestral de de menor profundidade na árvore que satisfaz . Para achar esse ancestral rapidamente, basta usarmos a técnica de binary lifting. Chamaremos esse vértice de . Tendo isso feito, para cada reunião , só precisamos considerar os descendentes de , não mais os ancestrais.
Tendo isso feito, podemos finalizar o problema de diversas maneiras. O jeito mais prático é fazer uma , mantendo alguma estrutura que permita somas e consultas (range sum, point query) em tempo logarítmico, como uma BIT ou uma Segment Tree. Pense nessas estruturas como um “vetor de frequência”, capaz de nos informar em quantas das reuniões que estão “ativas” (para um dado vértice , por conta da , só estarão “ativas” as reuniões que tem como um dos ancestrais de ou o próprio ) uma dada fortuna idade está contida. Quando “entrarmos” num vértice, somaremos, na nossa estrutura, no intervalo para toda reunião que tem como o nó atual. Para acharmos a resposta de um dado vertice , basta fazermos uma consulta na estrutura, obtendo a quantidade de reuniões em que está contida sua fortuna. Quando “sairmos” do nó, devemos desativar essas reuniões, subtraindo .