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 .