OBI 2021 - Fase 2 Turno B - 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!
Comentário escrito por Lúcio Figueiredo, Pedro Racchetti, Thiago Mota e Luca Dantas
Para conferir a prova na íntegra, clique aqui.
Cálculo rápido
Conhecimento prévio necessário:
Para resolver este problema, precisamos pensar em como podemos calcular a soma dos dígitos de um número de forma eficiente. Ou seja, dado um número , precisamos encontrar cada um dos dígitos de individualmente; feito isso, é fácil calcular a sua soma.
Algortimo para encontrar os dígitos de
Para extraírmos cada um dos dígitos de , podemos utilizar o seguinte algoritmo:
- Encontre o último dígito de ;
- Remova o dígito de . Denote o número resultante por ;
- Se não possuir dígito algum, termine o algoritmo. Senão, repita o passo 1 com o novo número .
Apesar do algoritmo acima estar correto, ainda é necessário descobrir como calcular o último dígito de e como removê-lo.
Encontrar o último dígito de
Seja o quociente e o resto na divisão de por . Com esta definição, temos que . Observe que como é um múltiplo de , o seu último dígito é igual a . Além disso, como é o resto da divisão de por , temos que . Portanto, podemos concluir que o valor é igual ao último dígito de em sua representação decimal! Para observar isto, considere os seguintes exemplos:
: . Último dígito de é igual a .
: . Último dígito de é igual a .
: . Último dígito de é igual a .
Com esta observação, concluímos que o último dígito de é igual a , já que o operador calcula o resto da divisão de um inteiro por outro.
Remover o último dígito de
Como é o último dígito de e , podemos observar que é igual ao valor - ou seja, o valor resultante da remoção do último dígito de . Novamente, considere os seguintes exemplos para melhor entendimento:
: . é igual a .
: . é igual a .
: . é igual a .
Para encontrar o valor , observe que, por definição, ele é igual a . Porém, como , temos que é igual à parte inteira de . Portanto, o valor é igual a , já que o operador calcula a parte inteira da divisão.
Agora que sabemos como executar cada passo do algoritmo apresentado acima, basta o utilizarmos para encontrar a soma dos dígitos de .
Complexidade do algoritmo
Observe que a complexidade do algoritmo acima é proporcional à quantidade de dígitos de . Portanto, como a quantidade de dígitos de é menor ou igual a , a complexidade final do algoritmo é .
Etapa final
Agora que possuímos um algoritmo eficiente para encontrar as somas dos dígitos de um número inteiro, o que sobra para terminarmos o problema é executá-lo para cada inteiro entre e , utilizando um loop For. Caso a soma dos dígitos de seja igual a , adicionamos em uma variável de resposta, e imprimimos o seu valor ao final do código.
Como executamos o algoritmo acima em todos os números entre e , a complexidade final da solução é . Para melhor entendimento, confira o código abaixo:
Lista palíndroma
Conhecimento prévio necessário:
Antes de resolver o problema, podemos fazer algumas observações iniciais. Para uma subarray , com da lista fornecida, note:
- Se , então o número de operações necessárias é ;
- Se , não precisamos fazer uma operação, bastando resolver o problema para a subarray ;
- Se , precisamos aumentar . Dessa forma, precisamos fazer uma operação de contração com o consecutivo de . A mesma ideia se aplica para o caso de , fazendo a operação entre e .
Podemos então usar as observações do algoritmo de Two Pointers indicado no link acima, usando como subarray o intervalo - ou seja, a lista inteira. Usaremos então como o primeiro ponteiro, começando no início da lista, e como segundo ponteiro, começando no fim da lista, até que se encontrem pelas operações acima.
A complexidade final da solução é . Segue o código comentado, para melhor compreensão:
Poligrama
Conhecimento prévio necessário:
A ideia para resolver este problema é utilizar o fato de que todas as substrings devem ter o mesmo tamanho, pois todas elas tem que ser um anagrama da raiz. Portanto, o tamanho da raiz tem que ser um divisor de . Iremos então pecorrer todos os tamanhos possíveis da raiz e testar se aquela raiz é uma resposta válida.
Para comparar se duas strings são anagramas, podemos olhar se as frequências de cada letra são as mesmas nas duas strings. Com isso, para cada possível tamanho da raiz, podemos olhar a string e conferir se todas as substrings são anagramas da raiz. Já que para cada divisor de , nós conferimos se ele é uma resposta em , a complexidade final é , sendo o número de divisores de .
Na aula de Teste de Primalidade, Divisores e Fatoração, podemos ver que o número de divisores de um número é por volta de (embora na maioria das vezes o número de divisores é bem menor do que este valor). Portanto, a complexidade final é . Segue a implementação do código:
Senha da Vó Zinha
Conhecimento prévio necessário:
Para a resolução desse problema precisamos analisar algumas propriedades da comparação lexicográfica de duas strings. Como a comparação vai da esquerda para a direita se nós sabemos que, se existe um conjunto de substrings com um prefixo menor lexicograficamente que outro conjunto de substrings , todos os elementos do conjunto aparecem antes na lista que todos os elementos do conjunto . Portanto, se analisarmos cada posição em que temos uma escolha a fazer, sabemos quantas possibilidades existem para cada escolha e a ordem relativa entre esses grupos.
Por exemplo, se temos a string e as palavras e , sabemos que existem três strings com o prefixo e três strings com o prefixo . Logo, podemos definir qual a letra de cada posição à medida que andamos da esquerda para a direita da seguinte maneira: Vamos dizer que queremos a -ésima permutação e temos possibilidades nessa posição, e, para cada uma dessas escolhas, temos possibilidades para o resto da string. Desta forma, sabemos que o número de strings possíveis no total é . Como sabemos a posição relativa de cada letra, podemos decidir qual das possibilidades é a correta olhando o valor de (0-indexado), pois se for uma das primeiras possibilidades, a letra correta é a primeira; se for entre as possibilidades , a letra correta é a segunda, e assim por diante. Depois que decidirmos a letra atual, basta continuarmos com o mesmo problema em versão reduzida para escolher a próxima letra, com o próximo valor de sendo (resto da divisão de por ).
O único detalhe final é que a quantidade de possibilidades pode ser muito grande, ultrapassando o limite de um inteiro de 64 bits (o valor pode chegar até ). Portanto, pode acontecer que em algumas das primeiras letras não seja possível calcular qual a letra utilizando o algoritmo apresentado acima. Porém, nós podemos nos fazer valer de que, como para um prefixo da string o valor de a letra a ser escolhida sempre será a primeira, só precisaremos responder qual a letra a ser escolhida para um sufixo da string. Para escolher esse sufixo basta percorrermos a string de trás pra frente e contar quantas possibilidades existem só no sufixo atual; caso esse valor seja maior ou igual a , encontramos o que queríamos e podemos começar o algoritmo. Senão, continuamos andando para a esquerda.
Segue o código para melhor entendimento: