OBI 2022 - Fase 3 - Programação Nível 2
Para se preparar para a OBI, confira o nosso Roteiro de Estudos, nele você vai encontrar um direcionamento completo de como estudar!
Para conferir a prova na íntegra, clique aqui.
Caravana
Escrito por Caique Paiva
Conhecimento Prévio Necessário:
Veja que, como queremos deixar todos os camelos com a mesma quantidade de carga, significa que todos os camelos terão carga de peso . Logo, a resposta pra cada
é
.
Clique aqui para conferir o código
Dígitos
Escrito por Vitor Veiga
Conhecimento prévio necessário:
Dada uma sequência de todos os dígitos presentes entre dois números quaisquer e
, queremos encontrar o menor
que satisfaz a sequência. A ideia para o problema será checar todos os possíveis
, de
até
algarismos, até achar um termo inicial que satisfaz as restrições da sequência.
Para isso, utilizaremos as strings para nos auxiliar, pela maior facilidade em adicionar e remover algarismos. Começamos com duas strings e
, que armazenam o número inicial e o próximo número que temos que achar para haver uma sequência válida, ou seja,
,
,
, e assim por diante. Sempre que acharmos o
, adicionamos uma unidade no número contido nele e continuamos nosso loop.
Para melhor entendimento da ideia, o passo a passo do primeiro caso teste:
e
O primeiro número inicial que testaremos será o :
(ok)
(ok)
(ok)
(absurdo)
Depois, testaremos :
(ok)
(absurdo)
Finalmente, testaremos :
(ok)
(ok)
fim da sequência
Note que caso continuássemos, também seria válida a sequência que inicia com o número , que só teria ele mesmo.
Segue o código:
Clique aqui para conferir o código
Pilha de Moedas
Escrito por Caique Paiva
Conhecimento Prévio Necessário:
Primeiro, vamos ordenar as pilhas por quantidade de moedas na ordem crescente. Por exemplo, a sequência de pilhas vai virar
. Obviamente isso não vai mudar a resposta final, porém vai nos ajudar a achar a resposta.
Como foi spoilado nos conteúdos, vamos fazer programação dinâmica!! A ideia principal do problema é que os valores mais altos nunca vão ser alterados, pois isso é obviamente não optimal (Por exemplo, no exemplo , não faz sentido colocarmos uma moeda em um dos oitos, por que isso vai sempre deixar a quantidade de moedas diferente maior).
Com isso, podemos fazer a operação ser: Pegar um intervalo e fazer com que todos os valores sejam iguais a
, e isso diminui a quantidade de valores distintos. Tal operação tem um custo de
.
Certo, mas por que fizemos essa mudança de operação? A primeira vista parece ser inútil, mas o ponto todo é que nunca vamos fazer essa operação duas vezes no mesmo índice, o que nos ajuda muito a fazer a .
Com isso, seja a quantidade de operações que precisamos para o intervalo
ter no máximo
valores distintos. Então
E os casos bases vão ser e se
.
Usando uma soma de prefixo para manter o valor de , o código vai rodar em
, pois para calcular toda a dp leva
de tempo, e a transição da dp é
. Tal complexidade passa, pois
.
Clique aqui para conferir o código
Fica de exercício para o leitor codar o problema com uma dp iterativa ao invés de recursiva.
Dona Minhoca
Escrito por Arthur Lobo
Conhecimento prévio necessário:
Veremos as salas como vértices e os túneis como as arestas de um grafo.
Parcial 1 (K = 1)
Para esta parcial, podemos posicionar apenas 1 radar, então ele deve ser posicionado em um vértice que minimiza a distância para o vértice mais distante. Esse vértice é o diâmetro da árvore, portanto o alcance do radar deve ser .
Parcial 2 (A aresta
liga os vértices
e
)
Nessa parcial, o grafo é uma linha, então, para um fixo, vamos posicionar os radares nas posições
. Basta que o último radar proteja até a última sala , portanto
.
Parcial 3 (
)
Para essa parcial, iremos passar por cada de 1 até
e ver qual é o primeiro que permite posicionarmos no máximo
radares.
Agora para um fixo, temos que saber qual a menor quantidade de radares que precisamos posicionar. Podemos provar que o seguinte algoritmo guloso funciona: Primeiro enraizaremos a árvore no vértice 1. Agora vamos colocar um radar em um vértice
apenas se for impossível continuarmos sem colocar. Ou seja, existe algum vértice na sua sub-árvore de
a uma distância
de
que não está protegido, então se não colocarmos nenhum novo radar em
e continuarmos subindo em direção à raiz, esse vértice estaria desprotegido.
- Seja
a menor distância de
para um vértice na sua sub-árvore que possui um radar.
- Seja
a maior distância de um vértice da sub-árvore de u que está desprotegido, ou seja, o vértice mais fundo que não é protegido por nenhum radar. Caso não exista nenhum vértice desprotegido,
.
Quando estamos vendo um vértice , primeiro resolvemos esse problema para todos os vértices na sua sub-árvore, ou seja, sendo
o conjunto dos filhos de
(vértices que tem uma aresta com u e não são seu ancestral), chamamos
para todo
, e então
e
(ou
se v é uma folha).
Se o radar mais próximo de consegue proteger o vértice mais longe de
desprotegido, ou seja, se
, então podemos falar que
, pois agora esse vértice que estava desprotegido (e todos os outros na sub-árvore, já que este é o mais longe) está protegido.
Se agora a distância do mais longe desprotegido for , precisamos colocar um radar no vértice
, pois se continuarmos subindo em direção a raiz não podemos mais protegê-lo. Então fazemos
,
e aumentamos a quantidade de radares usados.
Por fim, se ao chegarmos na raiz existir algum vértice desprotegido, então aumentamos a quantidade de radares novamente. A complexidade fica . Segue o código para melhor entendimento:
Clique aqui para conferir o código
Parcial 4 (Sem restrições adicionais)
É fácil ver que se podemos posicionar no máximo radares com raio
, então também podemos posicionar no máximo
com raio
. Portanto, podemos fazer uma busca binária para encontrarmos qual o menor
viável, a complexidade final fica
.
Construção de Rodovia
Escrito por Arthur Lobo
Conhecimento prévio necessário:
Observação: Para melhor entendimento, diremos que um par é conexo se existe um caminho de
para
.
O problema diz que precisamos encontrar algum par de vertices tal que não existe aresta de
para
e não exista nenhum par
tal que
não é conexo, mas se tornaria conexo caso a aresta
fosse criada. Para chegarmos na solução completa, precisamos fazer uma série de observações e reduções.
Primeiramente, podemos reduzir as condições do problema para:
- Devemos encontrar um par
tal que
seja conexo e não existe a aresta
.
Isso é verdade pois se não é conexo, então, criando essa aresta, o par
se tornaria conexo, contradizendo o enunciado; e se
é conexo, então criando essa aresta nenhum novo par se torna conexo, pois qualquer par que fosse usar essa aresta para ir de
até
, poderia usar o caminho que já existia.
Parcial 1 (Existe um caminho entre todo par de vértices)
Nessa parcial, temos que todo par de vértices é conexo, então só precisamos encontrar um par de vértices que não possui aresta de
para
, já que sabemos que existe um caminho de
para
.
Para fazermos isso, primeiramente precisamos encontrar algum vértice que tem menos de
arestas saindo dele, ou seja, que não liga para todo os outros vértices. Depois passamos por todos os outros vértices para descobrirmos algum vértice
tal que não existe a aresta
. Se não existir nenhum vértice
satisfazendo com menos de
arestas saindo dele, então a resposta é
.
Parcial 2 (se existe uma rodovia de
para
, então também existe uma rodovia de
para
)
Nessa Parcial, temos que as arestas são bidirecionais, então todos os pares de vértice dentro de uma componente conexa são conexos. Portanto, apenas precisamos encontrar um par de vértices tal que
e
estão na mesma componente conexa e a resta
não existe.
Podemos usar uma ideia parecida com a da parcial anterior: precisamos encontrar algum vértice tal que existam menos de (tamanho da componente conexa de
)
arestas saindo dele, ou seja, não liga para todos os outros vértices da componente conexa dele. Depois passamos por todos os outros vértices para descobrirmos algum vértice
que está na mesma componente de
e não exista a aresta
. Assim como na parcial anterior, se não existir
que satizfaz a condição, a resposta é
.
Parcial 3 (Sem restrições adicionais)
Podemos reduzir a condição ainda mais:
- Devemos encontrar uma tripla
tal que as arestas
e
existem e a aresta
não existe.
Conseguimos provar isso por indução: digamos que agora estamos vendo o par e ele seja válido (é uma resposta), e o último vértice no caminho de
para
antes de
é
. Se a aresta
existe, então chegamos na nossa condição. Se a aresta
não existe, então o par
também é uma resposta. Portanto, podemos mudar o par que estamos vendo agora para
e continuar fazendo isso até que cheguemos na nossa condição.
Até agora nossa solução é: primeiramente vamos fixar o da nossa tripla
(vamos ver todos os
). Agora, para cada
tal que existe a aresta
, veremos todos os
tal que existe a aresta
(ou seja, veremos todas as triplas
que existe a aresta
e
). Podemos verificar em
se a tripla
é válida, ou seja, se a aresta
não existe, utilizando um vetor de frequência ou um
.
A complexidade dessa solução é e podemos vizualizá-la da seguinte forma: seja
o grau de entrada de
,
o grau de saída de
, e
a quantidade de operações do algoritmo com
arestas. Note que
é igual ao somatório de
, para todo
, pois para cada
nós verificamos todos os pares
em que
chega em
(
opções) e
sai de
(
opções), portanto teremos:
\begin{equation*} T(M) = \sum_{1 \leq w \leq N} (G_{in}(w)*G_{out}(w)) \leq \sum_{1 \leq w \leq N}(G_{in}(w)*M) \Rightarrow T(M) = M* \sum_{1 \leq w \leq N}(G_{in}(w)) \Rightarrow T(M) \leq M^2 \end{equation*}
Portando, a complexidade do nosso algoritmo é . Mas será que podemos deixar a complexidade ainda melhor que isso?
E se terminássemos o código assim que encontramos uma resposta? Ou seja, assim que encontramos que satisfaz nossas condições, instantaneamente retornamos esse par e terminamos o código. Podemos provar que a complexidade do algoritmo com essa otimização é
, para isso vamos usar uma técnica chamada sqrt decomposition, que consiste em separar elementos (nesse caso, vértices) entre pesados e leves.
- Pertencerão ao conjunto
os vértices leves, que serão todos os vértices
tal que
.
- Pertencerão ao conjunto
os vértices pesados, que serão todos os vértices
tal que
. Sabemos que
, pois, tendo em vista que a quantidade total de arestas é M, temos que:
\begin{equation*} |P|*\sqrt M \leq \sum_{x \in P} G_{in}(x) <= M \Rightarrow |P|*\sqrt M <= M \Rightarrow |P| <= \sqrt M \end{equation*}
Agora vamos analisar a complexidade de quando o vértice fixado (ou seja, o vértice a partir do qual vamos ver os pares
) é do conjunto
e quando é do conjunto
:
Seja a quantidade total de operações feitas quando o
é do conjunto
e temos
arestas:
\begin{equation*} T_L(M) = \sum_{x \in L}(G_{in}(x)*G_{out}(x)) \leq \sum_{x \in L}(\sqrt M*G_{out}(x)) \Rightarrow T_L(M) \leq \sqrt M*\sum_{x \in L}(G_{out}(x)) \Rightarrow T_L(M) <= \sqrt M*M \end{equation*}
Para qualquer , sempre verificaremos no máximo
pares, pois se nos
primeiros pares
existia uma aresta de
para
, então o
ésimo par
será a resposta. Já que todos os pares
que vemos são diferentes, nas primeiras
iterações vemos todos os pares que correspondem à arestas do grafo, então o
ésimo par não pode corresponder à uma aresta pelo Princípio da Casa dos Pombos. Portanto, cada
fará o mínimo entre
e
operações.
Seja a quantidade de operações feitas quando o
é do conjunto
e temos
arestas:
\begin{equation*} T_P(M) = \sum_{x \in P}min(G_{in}(w)*G_{out}(w),M+1) <= |P|*(M+1) \Rightarrow T_P(M) <= \sqrt M*M + \sqrt M \end{equation*}
Agora somamos a quantidade de operações de quando faz parte do conjunto
e quando faz parte do conjunto
para encontrarmos a quantidade total de operações
:
\begin{equation*} T(M) = T_L(M) + T_P(M) \leq \sqrt M*M + \sqrt M*M + \sqrt M \Rightarrow T(M) \leq 2*\sqrt M*M +\sqrt M \end{equation*}
Portanto, a quantidade total de operações com M arestas é no máximo , então a complexidade da nossa solução é
.
Para que o código fique mais simples e possamos verificar se existe a aresta , vamos fixar o
da tripla
, e não o
, mas isso não muda a analise de complexidade. Segue o código para melhor entendimento: