Spoj NUMTSN: 369 Numbers (link para o problema)
Codeforces 2015-2016 Petrozavodsk Winter Training Camp: Maximum Product (link para o problema)
No primeiro problema, queremos contar quantos números 369 existem entre A e B inclusivo. Esse números tem:
- pelo menos um dígito 3;
- o mesmo número de dígitos 3, 6 e 9.
O problema é que os números vão até 10^50. O problema é muito parecido com o da soma da parte 1, a diferença é que, ao invés de olhar a soma, precisamos olhar o número de dígitos 3, 6 e 9. Podemos computar de 0 a B e depois de 0 a A e subtrair o resultado (precisamos checar também o caso A, já que B-A não engloba esse caso).
O segundo problema é um pouco mais difícil, queremos saber o número entre A e B cujo o produto dos dígitos é o máximo possível -- por exemplo, 319 tem produto 3*1*9 = 27, e 101 tem produto 1*0*1 = 0. Pra isso, primeiro computamos o produto máximo, redefinindo a nossa PD. Uma ressalva é que zeros à esquerda do primeiro dígito não contam, por isso precisamos armazenar na PD se o primeiro dígito já foi usado (se não, podemos colocar 0 sem multiplicar o produto por 0).
Existem vários jeitos de resolver problemas com DP com dígitos, essa é uma das opções! A complexidade é (tamanho da matriz dp) x (10 dígitos ou mais), pois cada elemento da matriz é computado uma única vez e depende de 10 opções. Outras são válidas e podem ser até mais rápidas ou mais lentas.
int dp[MAXN][MAXN][MAXN][MAXN]; // Matriz preenchida com -1 inicialmente
int calc(int digit, int t, int s, int n) {
if (digit == 0) {
return (t >= 1 && s == t && s == n) ? 1 : 0;
}
int& ans = dp[digit][t][s][n];
if (ans >= 0) return ans;
ans = 0;
for (int i = 0; i <= 9; i++) {
// Ve o que acontece quando adiciona o digito i
// t, s, n somam +1 se i for 3, 6 ou 9
ans += calc(digit-1, t + (i == 3), s + (i == 6), n + (i == 9));
ans %= MOD;
}
return ans;
}
int compute(const char *numero, int n) {
int ans = 0;
// Numero acumulado de tres, seis e noves
int three = 0, six = 0, nine = 0;
for (int i = 0; i < n; i++) {
int digit = numero[i] - '0';
for (int d = 0; d < digit; d++) {
ans += calc(n-i-1, three + (d == 3), six + (d == 6), nine + (d == 9));
ans %= MOD;
}
three += (digit == 3); six += (digit == 6); nine += (digit == 9);
}
return ans;
}
O segundo problema é um pouco mais difícil, queremos saber o número entre A e B cujo o produto dos dígitos é o máximo possível -- por exemplo, 319 tem produto 3*1*9 = 27, e 101 tem produto 1*0*1 = 0. Pra isso, primeiro computamos o produto máximo, redefinindo a nossa PD. Uma ressalva é que zeros à esquerda do primeiro dígito não contam, por isso precisamos armazenar na PD se o primeiro dígito já foi usado (se não, podemos colocar 0 sem multiplicar o produto por 0).
typedef long long ll;
ll dp[MAXN][2][2][2]; // Matriz preenchida com -1 inicialmente
ll calc(int d, int moreA, int lessB, int firstDigit) {
if (d == 0) return 1LL;
ll& ans = dp[d][moreA][lessB][firstDigit];
if (ans >= 0) return ans;
int sup = lessB ? 9 : (superior[d] - '0'); // d-esimo digito do teto
int inf = moreA ? 0 : (inferior[d] - '0'); // d-esimo digito do piso
ans = 0;
for (int i = inf; i <= sup; i++) {
ll mult = 1;
// Se o primeiro digito nao foi usado e i == 0, entao ignoramos a multiplicacao
if (firstDigit == 1 || (i > 0)) mult = i;
// Resposta eh o maximo entre valores anteriores e o obtido adicionando i
ans = max(ans, mult*calc(d-1, moreA | (i > inf), lessB | (i < sup),
firstDigit | (i > 0)));
}
return ans;
}
Para reconstruir o número, precisamos repetir as escolhas que maximizam esse produto. Portanto, testamos o que acontece quando adicionamos um número: se o valor encontrado for igual ao do máximo produto, então mantemos, e fazemos isso para cada um dos dígitos. O código fica parecido com o que está abaixo.void reconstruct(int d, int moreA, int lessB, int firstDigit) {
if (d == 0) return;
int sup = lessB ? 9 : (superior[d] - '0'); // d-esimo digito do teto
int inf = moreA ? 0 : (inferior[d] - '0'); // d-esimo digito do piso
for (int i = inf; i <= sup; i++) {
ll mult = 1;
if (firstDigit == 1 || (i > 0)) mult = i;
ll val = mult*calc(d-1, moreA | (i > minV), lessB | (i < maxV),
firstDigit | (i > 0));
// Se o valor obtido colocando i for igual ao maximo, adicionamos i
if (val == calc(d, moreA, lessB, first)) {
if (nonZero || (i > 0)) cout << i; // Printa i
// Reconstruir o resto dos digitos
reconstruct(d-1, moreA | (i > inf), lessB | (i < sup),
firstDigit | (i > 0));
return;
}
}
}
Existem vários jeitos de resolver problemas com DP com dígitos, essa é uma das opções! A complexidade é (tamanho da matriz dp) x (10 dígitos ou mais), pois cada elemento da matriz é computado uma única vez e depende de 10 opções. Outras são válidas e podem ser até mais rápidas ou mais lentas.
Nenhum comentário:
Postar um comentário