La complejidad del tiempo de esta pregunta se diferencia de una pregunta similar que se ha pedido. Esta es una pregunta de Zauba desarrollador contratación de desafío (evento terminó hace un mes):
f(0) = p
f(1) = q
f(2) = r
for n > 2
f(n) = a*f(n-1) + b*f(n-2) + c*f(n-3) + g(n)
where g(n) = n*n*(n+1)
p, q, r, a, b, c, n
son dados. n
puede ser tan grande como 10^18
.
En el enlace anterior, la complejidad del tiempo no se ha especificado y ya he resuelto este problema en O(n)
el pseudocódigo está por debajo (sólo una aproximación, todos los posibles límites, y casos extremos se manejaron en el certamen).
if(n == 0) return p;
if(n == 1) return q;
if(n == 2) return r;
for(long i=3;i<=n;i++){
now = a*r + b*q + c*p + i*i*(i+1);
p = q; q = r; r = now;
}
Tenga en cuenta que tengo de módulo usado 10^9 + 7
siempre que sea apropiado en el código original a desbordamientos de mango, maneja casos extremos apropiados siempre que sea necesario y que he utilizado java tipo de datos de longitud (si ayuda).
Pero como esto todavía requiere O(n)
tiempo, estoy esperando una mejor solución que puede manejar n ~ 10^18
.
EDITAR
Como usuario גלעד ברקן mencionado acerca de su relación con la potenciación de la matriz, que he tratado de hacer esto y pegado en un punto determinado, donde no estoy seguro de qué lugar en la cuarta fila, tercera columna de la matriz. La amabilidad de hacer todas las sugerencias y correcciones.
| a b c 1? | | f(n) | | f(n+1) |
| 1 0 0 0 | |f(n-1)| | f(n) |
| 0 1 0 0 | |f(n-2)| => | f(n-1) |
| 0 0 ?! 0 | | g(n) | | g(n+1) |
M A B
exponenciación matriz es de hecho el camino correcto a seguir, pero hay un poco más de trabajo que hacer.
Desde g(n)
no es constante de valor, no hay manera de aplicar la exponenciación matriz de manera eficiente ( O(log n)
en lugar de O(n)
) a la relación de recurrencia en su forma actual.
Una relación de recurrencia similares necesita ser encontrado para g(n)
con sólo una trailing término constante. Dado que g(n)
es cúbico, se requieren 3 términos recursivos:
g(n) = x*g(n-1) + y*g(n-2) + z*g(n-3) + w
Ampliar las expresiones cúbicos para cada uno de ellos:
n³ + n² = x(n³-2n²+n) + y(n³-5n²+8n-4) + z*(n³-8n²+21n-18) + w
= n³(x+y+z) + n²(-2x-5y-8z) + n(x+8y+21z) + (w-4y-18z)
Coinciden con los coeficientes para obtener tres ecuaciones simultáneas para x, y, z
más otro para calcular w
:
x + y + z = 1
-2x - 5y - 8z = 1
x + 8y + 21z = 0
w - 4y - 18z = 0
Resolverlos para obtener:
x = 3 y = -3 z = 1 w = 6
Convenientemente, estos coeficientes son también números enteros *, lo que significa la aritmética modular se puede realizar directamente en la recurrencia.
* Dudo que esto era una coincidencia - que bien podría haber sido la intención del examinador contratación.
Por tanto, la ecuación de recurrencia matriz es:
| a b c 1 0 0 0 | | f(n-1) | | f(n) |
| 1 0 0 0 0 0 0 | | f(n-2) | | f(n-1) |
| 0 1 0 0 0 0 0 | | f(n-3) | | f(n-2) |
| 0 0 0 3 -3 1 6 | x | g(n) | = | g(n+1) |
| 0 0 0 1 0 0 0 | | g(n-1) | | g(n) |
| 0 0 0 0 1 0 0 | | g(n-2) | | g(n-1) |
| 0 0 0 0 0 0 1 | | 1 | | 1 |
La ecuación final matriz exponenciación es:
[n-2]
| a b c 1 0 0 0 | | f(2) | | f(n) | | f(2) | | r |
| 1 0 0 0 0 0 0 | | f(1) | | f(n-1) | | f(1) | | q |
| 0 1 0 0 0 0 0 | | f(0) | | f(n-2) | | f(0) | | p |
| 0 0 0 3 -3 1 6 | x | g(3) | = | g(n+1) | , | g(3) | = | 36 |
| 0 0 0 1 0 0 0 | | g(2) | | g(n) | | g(2) | | 12 |
| 0 0 0 0 1 0 0 | | g(1) | | g(n-1) | | g(1) | | 2 |
| 0 0 0 0 0 0 1 | | 1 | | 1 | | 1 | | 1 |
(Cada operación es implícitamente modulo 10^9 + 7
o el que sea dicho número se suministra).
Tenga en cuenta que Java %
operador es el resto , que es diferente a la de módulo para los números negativos. Ejemplo:
-1 % 5 == -1 // Java
-1 = 4 (mod 5) // mathematical modulus
La solución es bastante simple:
long mod(long b, long a)
{
// computes a mod b
// assumes that b is positive
return (b + (a % b)) % b;
}
El original algoritmo iterativo:
long recurrence_original(
long a, long b, long c,
long p, long q, long r,
long n, long m // 10^9 + 7 or whatever
) {
// base cases
if (n == 0) return p;
if (n == 1) return q;
if (n == 2) return r;
long f0, f1, f2;
f0 = p; f1 = q; f2 = r;
for (long i = 3; i <= n; i++) {
long f3 = mod(m,
mod(m, a*f2) + mod(m, b*f1) + mod(m, c*f0) +
mod(m, mod(m, i) * mod(m, i)) * mod(m, i+1)
);
f0 = f1; f1 = f2; f2 = f3;
}
return f2;
}
funciones de matriz Modulo:
long[][] matrix_create(int n)
{
return new long[n][n];
}
void matrix_multiply(int n, long m, long[][] c, long[][] a, long[][] b)
{
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
long s = 0;
for (int k = 0; k < n; k++)
s = mod(m, s + mod(m, a[i][k]*b[k][j]));
c[i][j] = s;
}
}
}
void matrix_pow(int n, long m, long p, long[][] y, long[][] x)
{
// swap matrices
long[][] a = matrix_create(n);
long[][] b = matrix_create(n);
long[][] c = matrix_create(n);
// initialize accumulator to identity
for (int i = 0; i < n; i++)
a[i][i] = 1;
// initialize base to original matrix
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
b[i][j] = x[i][j];
// exponentiation by squaring
// there are better algorithms, but this is the easiest to implement
// and is still O(log n)
long[][] t = null;
for (long s = p; s > 0; s /= 2) {
if (s % 2 == 1) {
matrix_multiply(n, m, c, a, b);
t = c; c = a; a = t;
}
matrix_multiply(n, m, c, b, b);
t = c; c = b; b = t;
}
// write to output
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
y[i][j] = a[i][j];
}
Y, por último, el nuevo algoritmo mismo:
long recurrence_matrix(
long a, long b, long c,
long p, long q, long r,
long n, long m
) {
if (n == 0) return p;
if (n == 1) return q;
if (n == 2) return r;
// original recurrence matrix
long[][] mat = matrix_create(7);
mat[0][0] = a; mat[0][1] = b; mat[0][2] = c; mat[0][3] = 1;
mat[1][0] = 1; mat[2][1] = 1;
mat[3][3] = 3; mat[3][4] = -3; mat[3][5] = 1; mat[3][6] = 6;
mat[4][3] = 1; mat[5][4] = 1;
mat[6][6] = 1;
// exponentiate
long[][] res = matrix_create(7);
matrix_pow(7, m, n - 2, res, mat);
// multiply the first row with the initial vector
return mod(m, mod(m, res[0][6])
+ mod(m, res[0][0]*r) + mod(m, res[0][1]*q) + mod(m, res[0][2]*p)
+ mod(m, res[0][3]*36) + mod(m, res[0][4]*12) + mod(m, res[0][5]*2)
);
}
Aquí están algunos puntos de referencia de la muestra para los dos algoritmos anteriores.
Original algoritmo iterativo:
n time (μs) ------------------- 10^1 9.3 10^2 44.9 10^3 401.501 10^4 3882.099 10^5 27940.9 10^6 88873.599 10^7 877100.5 10^8 9057329.099 10^9 91749994.4
Nuevo algoritmo de matriz:
n time (μs) ------------------ 10^1 69.168 10^2 128.771 10^3 212.697 10^4 258.385 10^5 318.195 10^6 380.9 10^7 453.487 10^8 560.428 10^9 619.835 10^10 652.344 10^11 750.518 10^12 769.901 10^13 851.845 10^14 934.915 10^15 1016.732 10^16 1079.613 10^17 1123.413 10^18 1225.323
El algoritmo antiguo tomó más de 90 segundos para calcular n = 10^9
, mientras que el nuevo algoritmo que logra en poco más de 0,6 mili segundos (un 150,000x aceleración)!
Complejidad temporal del algoritmo original era evidentemente lineal (como se esperaba); n = 10^10
tomó demasiado tiempo para completar así que no continué.
Complejidad temporal del nuevo algoritmo era evidentemente logarítmica - duplicando el orden de magnitud del n
dado lugar a la duplicación tiempo de ejecución (de nuevo, como se esperaba debido a la exponenciación por cuadratura).
Para los valores "pequeñas" de n
( < 100
) la sobrecarga de la asignación y las operaciones de matriz eclipsados el algoritmo en sí, pero rápidamente se convirtió en insignificante como n
aumentado.