Vamos agora ver como resolver de outra forma então, mais genérica, mas que pode ser mais lenta.
Como exemplo, vamos usar o problema: quantos números menores que X e maiores que Y existem tal que a soma dos dígitos é exatamente 20.
Modificando a recursão
Vamos voltar ao exemplo da parte 1: quantos números menores que 03234 existem cuja soma dos digitos é exatamente 20.
O primeiro dígito só pode ser 0, enquanto o segundo pode ser 0, 1, 2 e 3. Se colocamos 0 ou 1 ou 2, perdemos a restrição de limitante (pois poderíamos preencher o resto dos números com 999), mas se colocamos 3, ainda temos a restrição (o resto deve ser menor que 234).
Na parte 1, definimos a PD que diz quantos números com n dígitos existem, e criamos uma função compute que chama a PD. Para remover a função compute, podemos adicionar na DP uma informação: estamos presos ao limitante ou não -- um bit (0 ou 1).
Dizemos que ehMenor é 0 se estamos preso a restrição, e que ehMenor passa a ser 1 quando perdemos a restrição (quando colocamos 2 no lugar de 3, por exemplo). Observe que ehMenor só passa de 0 para 1, mas nunca de 1 para 0.
int dp[N][20][2];
char numero[5] = "03234";
memset(dp, -1, sizeof(dp)); // Seta a matriz -1
int calc(int digit, int sum, int ehMenor) {
if (sum < 0) return 0; // soma abaixo do valor
if (digit == 0) return (soma == 0); // retorna 1 se soma for 0, e 0 caso contrario
int& ans = dp[digit][sum][ehMenor]; // Cria uma referencia pro valor
if (ans != -1) return ans; // Se diferente de -1, ja foi computado
ans = 0;
int limit;
// Se estamos fora do limitante, podemos colocar qualquer digito ate 9
if (ehMenor == 1) limit = 9;
// Se nao, limite com base no valor do limitante superior
else limit = numero[digit] - '0';
// Avaliamos todos os digitos de 0 ate o limite
for (int d = 0; d <= limit; d++) {
int novoEhMenor;
// Caso em que ehMenor passa de 0 para 1
if (ehMenor == 0 && d < limit) novoEhMenor = 1;
else novoEhMenor = ehMenor;
ans += calc(digit-1, sum - d, novoEhMenor);
}
return ans;
}
int main() {
calc(5, 20, 0); // 5 digitos, soma 20, ehMenor comeca igual a 0
}
Essa PD já trata então todos os números abaixo do limitante superior!
Adicionando o limitante inferior
Digamos que temos o mesmo problema, mas queremos os números entre 01222 e 03234. O primeiro dígito é 0, e o segundo agora só pode ser 1, 2 ou 3.
Se colocamos 1, os demais devem ser maior que 222, se colocamos 2 eles podem ser qualquer coisa (de 000 até 999), e se colocamos 3 eles devem ser menor que 234). Portanto, temos um limitante inferior. Alterar a solução anterior não é complicado.
int dp[N][20][2][2];
char superior[5] = "03234";
char inferior[5] = "01222";
memset(dp, -1, sizeof(dp)); // Seta a matriz -1
int calc(int digit, int sum, int ehMenor, int ehMaior) {
if (sum < 0) return 0; // soma abaixo do valor
if (digit == 0) return (soma == 0); // retorna 1 se soma for 0, e 0 caso contrario
int& ans = dp[digit][sum][ehMenor][ehMaior];
if (ans != -1) return ans; // Se diferente de -1, ja foi computado
ans = 0;
int inf, sup;
// Se estamos abaixo do teto, podemos colocar qualquer digito ate 9
if (ehMenor == 1) sup = 9;
// Se nao, limite superior com base no valor do teto
else sup = superior[digit] - '0';
// Se estamos acima do piso, podemos colocar qualquer digito acima de 0
if (ehMaior == 1) inf = 0;
// Se nao, limite inferior com base no valor do piso
else inf = inferior[digit] - '0';
// Avaliamos numeros entre inf e sup
for (int d = inf; d <= sup; d++) {
int novoEhMenor, novoEhMaior
if (ehMenor == 0 && d < sup) novoEhMenor = 1;
else novoEhMenor = ehMenor;
if (ehMaior == 0 && d > inf) novoEhMaior = 1;
else novoEhMaior = ehMaior
ans += calc(digit-1, sum - d, novoEhMenor, novoEhMaior);
}
return ans;
}
int main() {
calc(5, 20, 0, 0); // 5 digitos, soma 20, ehMenor/ehMaior comeca igual a 0
}
O código está grande assim para ficar mais claro, mas ele pode ser encurtado! A função abaixo faz a mesma coisa.
int calc(int digit, int sum, int ehMenor, int ehMaior) {
if (sum < 0) return 0; // soma abaixo do valor
if (digit == 0) return (soma == 0); // retorna 1 se soma for 0, e 0 caso contrario
int& ans = dp[digit][sum][ehMenor][ehMaior];
if (ans != -1) return ans; // Se diferente de -1, ja foi computado
ans = 0;
int sup = (ehMenor == 1) ? 9 : superior[digit] - '0';
int inf = (ehMaior == 1) ? 0 : inferior[digit] - '0';
for (int d = inf; d <= sup; d++) {
// e se dissermos que 4 eh um digito proibido?
// basta fazer if (d == 4) continue; e ele sera ignorado
ans += calc(digit-1, sum - d, ehMenor | (d < sup), ehMaior | (d > inf));
}
return ans;
}
OBS: Uma desvantagem desse jeito é que a PD depende dos limites! Do jeito anterior da parte 1, podemos precomputar a PD e em seguida calcular para diferentes limites mais rapidamente.
Problema sugerido:
CS Academy: Banned digits [link]
Continuar para parte 3 [link].
Códigos no github [link]
Nenhum comentário:
Postar um comentário