OBOI 2023 - Fase 2 - Nível Sênior
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 nossos materiais.
Para conferir a prova na íntegra, clique aqui.
Cifra
Comentário escrito por João Pedro Castro
Conhecimento prévio necessário:
Vamos denotar o i-ésimo caractere de uma string como
(0-indexado). A ideia da solução é simplesmente checar se a "distância" entre todos os pares de caracteres
e
são a mesma para todo
. Para realizar isso, podemos simplesmente guardar a distância entre os primeiros caracteres de ambas strings,
e
, e depois adicionar essa distância à todo
e confirmar se após isso ele é o mesmo caractere que
.
Para não termos distâncias negativas, se dizemos que
e que
simultaneamente (ou seja, trocamos elas de lugar); e para somar essa distância
nos
podemos transformar eles em valores númericos entre
e
fazendo
, e então somar a esse valor a distância
, e depois pegar o módulo
disso, para que caso passe da letra Z ele volte para o A. Logo, o caractere após a soma vai ser
, e se esse valor for igual a
para todo
, a resposta é 'S', caso contrário a resposta é 'N'.
Clique aqui para conferir o código
Permutação Máxima
Comentário escrito por Caique Paiva
Conhecimentos Prévios Necessários:
- Algoritmos Gulosos
A ideia do problema é bem simples. Depois de fazer alguns casos na mão (), você vê que a resposta nesses casos vai ser a permutação
(para
é a resposta, para
é a resposta, e assim vai). Durante a prova, a ideia não é provar isso, mas você codar e torcer para isso ser a resposta, já que não tem penalidade para WA. Dito isso, é possível provar, embora ela seja meio complicada, então só recomendo pensar na prova caso você também se interesse por matemática olímpica.
Para implementar, basta criar uma variável , e fazer um for de
até
fazendo
. Veja que
pode ficar bem grande, então é importante criar ele como uma variável long long.
Frações Binárias
Comentário escrito por Murilo Maeda
Conhecimento prévio necessário
Solução
A observacão principal que é necessária nesse problema é que uma fração cujo denominador e numerador são potências de 2 pode ser escrita simplesmente como uma potência de 2. Por exemplo, uma fração pode ser escrita como
. Agora, temos que achar o intervalo de um produto de potências de 2 com o maior produto possível. Para fazer isso, basta perceber que quando multiplicamos potências de mesma base, simplesmente somamos os expoentes para obter o resultado. Mais especificamente,
. Então, se quisermos o intervalo com maior produto, basta encontrar o intervalo com maior soma dos expoentes. Ou seja, basta fazer uma dp de Kadane nos expoentes!
Cuidados Adicionais
Um problema que ainda temos é que obtemos a resposta em termos do expoente do resultado final. Como há no máximo frações e o expoente pode ir até
, o expoente da resposta pode chegar até
, que ultrapassa o limite de long long. Então, não é possível simplesmente calcular a resposta elevando 2 à resposta que achamos da dp e depois tirar módulo
, precisamos ir calculando a resposta aos poucos e ir calculando o módulo. Note que só podemos tirar módulo de produtos dessa forma porque
é primo.
Clique aqui para conferir o código
Árvore com Macacos
Comentário escrito por Arthur Lobo
Conhecimento prévio necessário:
A ideia é enraizar a árvore no vértice 1 e fazer uma DP em árvore que vai ser maior quantidade de vértices bloqueados na subárvore do
, com
representando alguma condição.
pode ser
ou
.
maior quantidade de bloqueados na subárvore do
e sem precisar mandar um macaco para o pai.
maior quantidade de bloqueados na subárvore do
e termina com um macaco no
(que pode ser mandado para o pai).
maior quantidade de bloqueados na subárvore do
e começa com uma pedra em
que foi mandada pelo pai.
Agora vamos olhar para as transições. Vão ter dois casos:
O primeiro é quanto o vértice começa com um macaco. Nesse caso:
, porque é impossível que o pai de
mande um macaco para ele, já que ele já começa com um.
. Porque
manda seu macaco para seu pai, então ele não pode interagir com os filhos.
, para todo
que é filho de
.
representa qual é o vértice que estamos mandando o macaco que começa em
.
O segundo caso é quanto o vértice não começa com um macaco:
, para todo
que é filho de
.
representa qual é o vértice que manda um macaco para
.
, para todo
que é filho de
.
representa qual é o vértice que estamos mandando o macaco
recebe.
, para todo
, tal que
e ambos são filhos de
.
e
representam, respectivamente, qual vértice manda um macaco para
e qual vértice recebe esse macaco mandado.
Em ambos os casos, também é necessário fazer (
se for o primeiro caso.
Clique aqui para conferir o código
Vingaça do Leonardo
Comentário escrito por Rafael Nascimento
Conhecimento prévio necessário:
Neste problema não existem muitas parciais interessantes, então comentaremos apenas a solução de 100 pontos. Porém, antes disso, precisamos fazer algumas observações. Vamos iniciar com a operação :
1º ) A operação dobra o tamanho da string. Trivial de provar, já que cada caractere é substituído por
caracteres.
2º) Existem no máximo operações de tamanho
. Trivial de provar considerando a observação
.
Agora vamos analisar as outras operações:
As operações e
são equivalentes ao std::deque, e algo fundamental é observar que não é possível alterar strings internas: ou seja, podemos adicionar caracteres apenas em áreas que ainda não foram "criadas" (as extremidades).
Não só isso, como podemos falar o seguinte:
Digamos que exista um array . Esse array representa o caractere que gerou o
. Por exemplo, se um caractere
foi dobrado
vezes, ou seja, virou
, afirmaremos que esses
caracteres foram gerados por
. Dessa forma o
de todos os caracteres é igual a
.
Agora definamos o array . Este array existe apenas para caracteres "originais", ou seja,
. Este array armazena o numero de vezes que o caracteres
foi afetado por operações de dobra. Observe que
Por fim, criamos o array . É possível provar que o array
é unimodal. Ou seja, ele primeiro é não-decrescente, atinge um pico
, e vira não-crescente. De forma mais técnica,
se
e
se
.
Essas informações já são suficientes para montarmos nossa programação dinâmica.
Primeiro, vamos analisar os estados. Temos que armazenar:
A posição atual que estamos na string, o nosso . Esse valor vai até
.
O valor atual de . Como dito anteriormente, esse valor vai até
.
Uma variável booleana indicando a direção do array (se ele está antes do pico, sendo não-decrescente, ou depois do pico, sendo não-crescente).
Para a transição nós temos que fazer as seguintes condições:
- O
alterou. Chamamos
. Devemos somar
caso a direção seja não-decrescente e subtrair
caso seja não-crescente.
- A direção se alterou de não-decrescente para não-crescente. Chamamos
. Note que esta troca acontece apenas uma vez.
- Caso a string seja válida, isto é, a substring que inclui
(ou seja, a substring gerada pelo caractere original) seja igual a substring esperada (a substring que seria gerado caso um caractere
ou
fosse submetido à
operações), podemos andar com a nossa função, indo para
. Podemos checar se a substring é válida com o hash, apenas tome cuidado pois o risco de colisão é alto. Provavelmente será necessário utilizar múltiplos hashes (de acordo com nossos testes, 5 hashes distintos são suficientes).
Calculando a complexidade:
Nós possuímos estados na nossa programação dinâmica. Considerando que caso implementemos um hash
podemos ter uma transição em
, a complexidade final é
. Soluções
devem receber AC, considerando que
.