Aula por Caique Paiva
Conhecimento prévio necessário:
- Combinatória Básica em Matemática
- Exponenciação Rápida
Alguns problemas bem comuns em programação envolvem contar quantidade de algumas coisas. Existem algumas maneiras de fazer isso, como com programação dinâmica, ou então, calcular uma fórmula explícita. Para isso, normalmente nós precisamos saber usar algumas funções, como fatorial ou binomiais, então, precisamos saber como calcular essas funções. Não vamos entrar nas provas do teoremas nessas aulas, mas todas elas podem ser encontradas aqui.
Definições
Primeiramente, defina (Chamamos de fatorial), indutivamente, definimos . Nós sabemos que esse número é a quantidade de permutações de um conjunto de elementos. Além disso, seja para ser
(Lê-se escolhe ). Sabemos que esse número representa a quantidade de maneiras de escolher elementos num conjunto de elementos.
Como calcular?
Primeiro, vamos olhar pros fatoriais. Veja que já é absurdamente grande e já passa do limite de inteiro, por isso, é normal os problemas pedirem para calcular as respostas módulo algum primo grande. Vamos chamar esse primo de . Então, queremos calcular módulo , e conseguimos fazer isso em ! Basta ver que , então, podemos montar uma recorrência! Veja o código para entender melhor a recorrência:
Veja que colocamos um vetor para salvar as respostas dos fatoriais. Isso é importante, pois nos problemas podemos chamar essa função várias vezes, e então, para não recalcular coisas, salvamos os números nesse vetor.
Agora, como vamos fazer para calcular binomiais? Pelo mesmo motivo dos fatoriais, é como os problemas pedirem para calcular as respostas módulo algum primo grande. Seja esse primo . Então, temos duas maneiras de calcular:
Forma Analítica
Sabemos que
Então, podemos calcular esse valor em , pois, sabemos colo calcular e , então, só precisamos calcular e módulo , mas podemos calcular isso usando o pequeno teorema de fermat (Prova desse teorema aqui). Para calcular módulo , basta ver que, se então
Então, precisamos calcular módulo , que com exponenciação rápida, podemos calcular em . Veja o código para entender melhor:
Forma Rercussiva
Sabemos pelo lema de Stifel que
Então, podemos montar uma dp para resolver essa recorrência! Vamos definir um vetor ch[][], os casos bases vão ser e para todo , e . Então, a transição vai ser
Veja o código para saber mais
Esse código roda em , porém, ele é bem mais fácil de codar, então quando o do problema for pequeno, vale a pena fazer esse método.
Recomendo você tentar codar essas funções por conta própria. Para submeter, use esse problema.
Exemplo
Dada uma string, sua tarefa é calcular o número de strings diferenets que podem ser criadas usando seus caracteres
Input
O único input é uma string de tamanho . Todos os caracteres estão entre a-z.
Output
Imprima o número de strings diferentes módulo .
Exemplo
Entrada | Saída |
aabac | 20 |
Entrada | Saída |
abcdef | 720 |
Vamos chamar de a quantidade de letras 'a' que tem na string. Por exemplo, no primeiro exemplo, . Então, vamos calcular a resposta. Primeiro, vamos escolher as posições para colocar os caracteres , então, temos possibilidades. Agora, para colocar os caracteres , temos possibilidades, pois não podemos colocar na mesma posição, e assim vai.
Em geral, a quantidade de maneiras que temos de colocar o caractere 'a' é , e então, a resposta vai ser a multiplicação de todas essas respostas.
Veja o código para ficar mais claro
Problemas Propostos
- Distributing apples
- Almost Identity Permutations
- Counting Orders
- Close Tuples
- Just Stalling
- Hits Different
- Bots
- Arena
- Help Yourself
Os problemas estão em ordem de dificuldade, mas a partir do Hits Different, os problemas ficam bem mais difíceis.