Desarreglos parciales: Implementando la tercera recurrencia

Figura
Figura. Recurrencia Desarreglos Parciales

Los desarreglos parciales D(n, k) de una lista con n elementos distintos son todas las sublistas ordenadas con n elementos distintos que se pueden generar de tal forma que solo k elementos aparecen en su posición original.

En un tema anterior vimos otra fórmula recurrente D(n, k) = n/k D(n-1, k-1) para obtener el valor numérico de los desarreglos parciales. Debido al cociente n/k, vimos allí las dificultades para usarla como base de implementación de un algoritmo para generar los desarreglos. Por eso en el siguiente tema buscábamos una nueva recurrencia que nos permitiera obtener el valor númerico de los desarreglos parciales:

b = n0 - k0
0 ≤ m ≤ b
D(b, m) = !b
D(n, k) = ∑j=k..n D(n-1, j-1)

Recordemos que el valor b = n0 - k0 se mantiene constante en la ejecución, pues proviene de la diferencia que inicialmente tienen las variables n, k. Vamos a usar esa base para implementar un algoritmo que genere los desarreglos parciales.

Algoritmo derangeP3(list, k) para generar desarreglos parciales

Usando como base esa recurrencia, implementamos el siguiente algoritmo para generar los desarreglos parciales:

// D(n, k) = sum_(j=k..n) D(n-1, j-1)
function derangeP3(list=[], k=0, p=-k, b=list.length-k) 
    let result = [];
    let n = list.length;
    if (n===b) {
        /* iter += Td(n-k) */
        result = derange1(list);
    } else {
        iter += 1;
        for (let j=k; j<=n; j++){
            iter += 1;
            let jk = j+p;
            let pivot = list[jk];
            iter += n-1;
            let listMinor = [ ...list.slice(0, jk), ...list.slice(jk+1)];
            let subResult = derangeP3(listMinor, j-1, p+1, b);
            /* subResult.length = !b * C(2n-j-b-1, n-b-1) */
            for (let i=0; i<subResult.length; i++) {
                /* subResult[i].length = n-1 */
                iter += 1 + subResult[i].length;
                result.push([...subResult[i].slice(0, jk), pivot,
                    ...subResult[i].slice(jk)]);
            }
        }
    }
    return result;
}

Con b = n0 - k0 constante durante toda la ejecución, pues se inicia con los valores iniciales n0 y k0, pasamos esa variable b como argumento en cada llamada con objeto de tenerlo disponible para el caso base n=b, en cuyo caso devolvemosTd(b) que son los desarreglos sin puntos fijos que obtuvimos con el primer algoritmo implementado derange1(list).

Usamos otro argumento p que iniciamos con p=-k que no intervendrá en la recurrencia, pues sólo sirve a efectos de seleccionar el pivote adecuado en el algoritmo. Como hacíamos con los algoritmos de recorte, recortamos la lista y llamamos nuevamente decrementanto el argumento j. E incrementando el argumento p para seleccionar otro pivote en la siguiente llamada.

No siempre es fácil ver esquemáticamente como evoluciona el algoritmo. Intentemos explicarlo usando el ejemplo D(abcd, 2) = {abdc, adcb, acbd, dbca, cbad, bacd}

D(abcd, 2)
    j=2 p=a -> D(bcd, 1)
        j=2 p=b -> D(cd, 1) => dc +=> bdc +=> abdc
        j=3 p=c -> D(bd, 2) => db +=> dcb +=> adcb
        j=4 p=d -> D(bc, 3) => cb +=> cbd +=> acbd
    j=3 p=b -> D(acd, 2)
        j=3 p=c -> D(ad, 2) => da +=> dca +=> dbca
        j=4 p=d -> D(ac, 3) => ca +=> cad +=> cbad
    j=4 p=c -> D(abd, 3)
        j=4 p=d -> D(ab, 3) => ba +=> bad +=> bacd

Result = abdc, adcb, acbd, dbca, cbad, bacd

Iteramos por el bucle j=k..n que en el ejemplo será j=2..4. El pivote se elige adecuadamente en cada iteración, desde el inicio de cada lista al inicio de cada bucle, pero luego se desplaza. Cuando llegamos a una lista con dos elementos estamos en el caso base y devolvemos => la lista invertida. Con +=> representamos la recomposición del resultado en las devoluciones de llamadas, que se produce en el bucle final que maneja subResult. Cada pivote se inserta en su sitio con respecto a la lista desde donde fue extraido. Al final obtenemos el resultado esperado, pues en cada desarreglo hay exactamente 2 pivotes en su sitio, es decir, 2 elementos están en su posición inicial mientras que el resto no lo están (están desarreglados).

Observando el algoritmo planteamos la recurrencia siguiente

T(n, k) = 1 + ∑j=k..n ( 1+(n-1) + T(n-1, j-1) + !b C(n-j+k-1, k-1) (1+(n-1) )

Si recordamos que los desarreglos parciales son D(n, k) = !b C(n, k), vemos que el tamaño de subResult es !b C(n-j+k-1, k-1) con fórmula similar a la dada. Observe que en el código pusimos el comentario

/* subResult.length = !b * C(2n-j-b-1, n-b-1) */

pues sabiendo que b=n-k es constante en la ejecución, tenemos que ese binomial que vemos en el comentario queda así, tal como pusimos antes en [1]:

!b C(2n-j-b-1, n-b-1) = !b C(n-j+n-(n-k)-1, n-(n-k)-1) = !b C(n-j+k-1, k-1)

Veáse que subResult es el resultado de la llamada let subResult = derangeP3(listMinor, j-1, p+1, b), con los argumentos D(n, k) = D(n-1, j-1), es decir, j-1 ≡ k-1, por lo que nos queda !b C(n-j+k-1, k-1) ∧ j=k ⇒ !b C(n-k+k-1, k-1) = !b C(n-1, k-1), que es el valor numérico de los desarreglos con la llamada D(n-1, k-1), manteniéndose b = n-1-(k-1) = n-k constante con la llamada inicial.

Simplificamos un poco [1]

T(n, k) = 1 + ∑j=k..n ( n + T(n-1, j-1) + !b n C(n-j+k-1, k-1) )

Esta recurrencia con dos variables n, k no es fácil de resolver. Para intentar algo podemos resolver lo siguiente, viendo que b = n - k ⇒ k = n - b y además tal como vimos en el comentario del código antes:

n-j+k-1 = n+n-n-j+k-1 = 2n-j-(n-k)-1 = 2n-j-b-1

Con esto la recurrencia queda así, dependiente sólo de n pues b es constante:

T(n, n-b) = 1 + ∑j=n-b..n ( n + !b n C(2n-j-b-1, n-b-1) + T(n-1, j-1) )

Despliegue con b=0

Usaremos la técnica del despliegue para ver si conseguimos una fórmula general. Empezamos con b=0, sabiendo que !0 = 1. Empezamos con la llamada inicial, identificando cada llamada recursiva con la variable i:

i = 0
T(n, n) = 1 + ∑j=n..n ( n + !0 n C(2n-j-1, n-1) + T(n-1, j-1) ) =
= 1 + ∑j=n..n ( n + !0 n C(2n-j-1, n-1) ) + ∑j=n..n T(n-1, j-1) =
= 1 + ∑j=n..n ( n + !0 n C(2n-j-1, n-1) ) + T(n-1, n-1)

Observe que no desplegamos el primer sumatorio, sólo el segundo que contiene la nueva llamada recursiva.

i = 1
T(n-1, n-1) = 1 + ∑j=n-1..n-1 ( (n-1) + !0 (n-1) C(2n-j-1, n-1) ) + T(n-2, n-2)
i = 2
T(n-2, n-2) =
1 + ∑j=n-2..n-2 ( (n-2) + !0 (n-2) C(2(n-1)-j-1, (n-1)-1) ) + T(n-3, n-3)

Ya vemos cierta consistencia por lo que vamos a suponer el caso cuando i=n-1

i = n-1
T(1, 1) =
1 + ∑j=n-(n-1)..n-(n-1) ((n-(n-1))+!0 (n-(n-1)) C(2(n-(n-1))-j-1, (n-(n-1))-1)) +
T(n-n, n-n)

Al llegar a la llamada n-1 encontramos el caso base T(n-n, n-n) = T(0, 0) = Td(0) = 3 cuyo valor es el del algoritmo derange1(list). Sumando las llamadas i podemos expresarlo así:

T(n, n) = (i=0..n-1 ( 1 + ∑j=n-i..n-i ( (n-i) +!0 (n-i) C(2(n-i)-j-1, (n-i)-1)) ) ) + 3

Si resolvemos con WolframAlpha obtendremos:

T(n, n) = n2+2n+3

Expandiendo en valores desde n=0 obtenemos T(n, n) = 3, 6, 11, 18, 27, 38, 51, 66, ... tal como se observa en la siguiente tabla, que se obtiene en la aplicación Combine Tools, donde resaltamos en amarillo la secuencia correspondiente a T(n, n) con b=n-k=0:

n↓k→01234567
031111111
146111111
211131111111
3395227181111
41732051424727111
59431116635309743811
66115727941911549588109511
7459935584031990120463268102115366
83934334866652827801051112925162351657207
9377028347424522790765104881128659263074110362552
1039996671510525313038720511527529317883468445712439318421

En verde tenemos la secuencia para T(n, n-1) con b=n-k=1 y en azul para T(n, n-2) con b=n-k=2 que desarrollaremos a continuación.

Despliegue con b=1

Recordemos la recurrencia

T(n, n-b) = 1 + ∑j=n-b..n ( n + !b n C(2n-j-b-1, n-b-1) + T(n-1, j-1) )

que particularizamos para b=1

T(n, n-1) = 1 + ∑j=n-1..n ( n + !1 n C(2n-j-2, n-2) + T(n-1, j-1) )

Empezamos con la llamada inicial:

i = 0
T(n, n-1) = 1 + ∑j=n-1..n ( n + !1 n C(2n-j-2, n-2) + T(n-1, j-1) ) =
= 1 + ∑j=n-1..n ( n + !1 n C(2n-j-2, n-2) ) + ∑j=n-1..n T(n-1, j-1) =
= 1 + ∑j=n-1..n ( n + !1 n C(2n-j-2, n-2) ) + T(n-1, n-2) + T(n-1, n-1)
i = 1
T(n-1, n-1) = 1 + ∑j=n-1..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
j=n-1..n-1 T(n-2, j-1) =
= 1 + ∑j=n-1..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) + T(n-2, n-2)

Y la otra llamada

T(n-1, n-2) = 1 + ∑j=n-2..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
j=n-2..n-1 T(n-2, j-1) =
= 1 + ∑j=n-2..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
T(n-2, n-3) + T(n-2, n-2)

Sumando ambas

T(n-1, n-1) + T(n-1, n-2) =
1 + ∑j=n-1..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
1 + ∑j=n-2..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) )
+
2 T(n-2, n-2) + T(n-2, n-3)
i = 2

Prescidiendo del desarrollo para abreviar obtenemos:

2 T(n-2, n-2) + T(n-2, n-3) =
2 + 2 ∑j=n-2..n-2 ( (n-2) + !1 (n-2) C(2(n-2)-j-2, (n-2)-2) ) +
1 + 1 ∑j=n-3..n-2 ( (n-2) + !1 (n-2) C(2(n-2)-j-2, (n-2)-2) )
+
3 T(n-3, n-3) + T(n-3, n-4)
i = 3

Prescidiendo del desarrollo para abreviar obtenemos:

3 T(n-3, n-3) + T(n-3, n-4) =
3 + 3 ∑j=n-3..n-3 ( (n-3) + !1 (n-3) C(2(n-3)-j-2, (n-3)-2) ) +
1 + 1 ∑j=n-4..n-3 ( (n-3) + !1 (n-3) C(2(n-3)-j-2, (n-3)-2) )
+
4 T(n-4, n-4) + T(n-4, n-5)
i = n-2

Ya podemos llegar a una solución con el caso base en la llamada i=n-2, y aunque no lo vamos a presentar para abreviar, pues solo tenemos que sumar todo lo resaltado desde i=0 hasta i=n-2

T(n, n-1) =
1 + ∑j=n-1..n ( n + !1 n C(2n-j-2, n-2) ) +
1 + ∑j=n-1..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
1 + ∑j=n-2..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
2 + 2 ∑j=n-2..n-2 ( (n-2) + !1 (n-2) C(2(n-2)-j-2, (n-2)-2) ) +
1 + 1 ∑j=n-3..n-2 ( (n-2) + !1 (n-2) C(2(n-2)-j-2, (n-2)-2) ) +
3 + 3 ∑j=n-3..n-3 ( (n-3) + !1 (n-3) C(2(n-3)-j-2, (n-3)-2) ) +
1 + 1 ∑j=n-4..n-3 ( (n-3) + !1 (n-3) C(2(n-3)-j-2, (n-3)-2) ) +
+ ... +
(n-2) + (n-2) ∑j=n-(n-2)..n-(n-2) ( (n-(n-2)) + !1 (n-(n-2)) C(2(n-(n-2))-j-2, (n-(n-2))-2)) ) +
1 + 1 ∑j=n-(n-3)..n-(n-2) ( (n-(n-2)) + !1 (n-(n-2)) C(2(n-(n-2))-j-2, (n-(n-2))-2)) ) +
(n-1) T(1, 1) + T(1, 0)

Por un lado tenemos el caso base en b=1 con T(1, 1) = T(1, 0) = Td(1) = 4, con lo que (n-1) T(1, 1) + T(1, 0) = 4 n. Por otro lado en lo anterior vemos dos grupos de sumas observando los índices de los sumatorios, un primer grupo con los que tienen índices como n-1..n, n-2..n-1, n-3..n-2, ...

1 + ∑j=n-1..n-0 ( n + !1 n C(2n-j-2, n-2) ) +
1 + ∑j=n-2..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
1 + 1 ∑j=n-3..n-2 ( (n-2) + !1 (n-2) C(2(n-2)-j-2, (n-2)-2) ) +
1 + 1 ∑j=n-4..n-3 ( (n-3) + !1 (n-3) C(2(n-3)-j-2, (n-3)-2) ) +
+ ... +
1 + 1 ∑j=n-(n-3)..n-(n-2) ( (n-(n-2)) + !1 (n-(n-2)) C(2(n-(n-2))-j-2, (n-(n-2))-2)) )

Y un segundo grupo con los que tienes índices como n-1..n-1, n-2..n-2, n-3..n-3, ...

1 + ∑j=n-1..n-1 ( (n-1) + !1 (n-1) C(2(n-1)-j-2, (n-1)-2) ) +
2 + 2 ∑j=n-2..n-2 ( (n-2) + !1 (n-2) C(2(n-2)-j-2, (n-2)-2) ) +
3 + 3 ∑j=n-3..n-3 ( (n-3) + !1 (n-3) C(2(n-3)-j-2, (n-3)-2) ) +
+ ... +
(n-2) + (n-2) ∑j=n-(n-2)..n-(n-2) ( (n-(n-2)) + !1 (n-(n-2)) C(2(n-(n-2))-j-2, (n-(n-2))-2)) )

Por lo tanto podemos aplicar una suma a la llamadas

T(n, n-1) =
i=0..n-1 ( 1 + ∑j=n-i-1..n-i ( (n-i) + !1 (n-i) C(2(n-i)-j-2, (n-i)-2) )) +
i=1..n-1 ( i + i ∑j=n-i..n-i ( (n-i) + !1 (n-i) C(2(n-i)-j-2, (n-i)-2) )) +
4n

Vea que podemos ampliar el segundo sumatorio a i=0 pues en ese caso el primer término será cero:

T(n, n-1) =
i=0..n-2 ( 1 + ∑j=n-i-1..n-i ( (n-i) + !1 (n-i) C(2(n-i)-j-2, (n-i)-2) )) +
i=0..n-2 ( i + i ∑j=n-i..n-i ( (n-i) + !1 (n-i) C(2(n-i)-j-2, (n-i)-2) )) +
4n

Para resolver esto y obtener algo más practicable observamos que !1 = 0

T(n, n-1) =
i=0..n-2 ( 1 + ∑j=n-i-1..n-i (n-i) ) +
i=0..n-2 ( i + i ∑j=n-i..n-i (n-i) ) +
4n

Resolvamos los sumatorios interiores j=n-i-1..n-i (n-i) = 2(n-i) y el otro sumatorio quedará como j=n-i..n-i (n-i) = n-i con lo que tenemos finalmente:

T(n, n-1) = (i=0..n-2 (1 + 2(n-i)) )+(i=0..n-2 (i + i(n-i))) + 4n

Podemos resolverlo con WolframAlpha obteniendo:

T(n, n-1) = 1/6 (n3+9n2+20n-6)

Expandiendo esta fórmula obtenemos la secuencia para n≥1 ⇒ T(n, n-1) = 4, 13, 27, 47, 74, 109, 153, 207, ... (ver en WA), la misma que vemos en la tabla de valores resaltados en verde más arriba.

Despliegue con b=2

Recordemos la recurrencia

T(n, n-b) = 1 + ∑j=n-b..n ( n + !b n C(2n-j-b-1, n-b-1) + T(n-1, j-1) )

que particularizamos para b=2

T(n, n-2) = 1 + ∑j=n-2..n ( n + !2 n C(2n-j-3, n-3) + T(n-1, j-1) )

Aunque he realizado el despliegue completo hasta b=4 (ver documento de trabajo en derangep3.txt), no voy a exponer todo eso pues es muy largo. Para abreviar, tenemos con b=1 el desglose de sumas que vimos en [1] y lo aplicamos a b=2:

T(n, n-2) =
i=0..n-3 ( 1 + ∑j=n-i-2..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
i=0..n-3 ( i + i ∑j=n-i-1..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
i=0..n-3 ( i(i+1)/2 + i(i+1)/2 ∑j=n-i..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
1/2 n(n-1) × 11

Llegando hasta b=4 se observa que los términos antes del sumatorio interno son de la forma

1, i, 1/2 i(i+1), 1/6 i(i+1)(i+2), 1/24 i(i+1)(i+2)(i+3)

que equivale a

C(i-1, 0), C(i, 1), C(i+1, 2), C(i+2, 3), C(i+3, 4)

Por otro lado el término 1/2 n(n-1) × 39 para los tres casos vistos b=0 a b=2 así como para b=3 y b=4 que he desplegado también:

b=0 ⇒ Td(0) = 3
b=1 ⇒ n × Td(1) = n × 4
b=2 ⇒ 1/2 n(n-1) × Td(2) = 1/2 n(n-1) × 11
b=3 ⇒ 1/6 n(n-1)(n-2) × Td(3) = 1/6 n(n-1)(n-2) × 39
b=4 ⇒ 1/24 n(n-1)(n-2)(n-3) × Td(4) = 1/24 n(n-1)(n-2)(n-3) × 173

Se identifica el término general con este sumatorio:

j=0..b C(n+j-b-1, j) × Td(b)

pues, por ejemplo para b=2 tenemos j=0..2 C(n+j-3, j) = 1/2 n(n-1). Con esto el despliegue para b=2 queda así con un forma más generalizada:

T(n, n-2) =
i=0..n-3 C(i-1, 0) ( 1 + ∑j=n-i-2..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
i=0..n-3 C(i, 1) ( 1 + ∑j=n-i-1..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
i=0..n-3 C(i+1, 2) ( 1 + ∑j=n-i..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
j=0..2 C(n+j-3, j) × 11

Resolvemos primero los sumatorios interiores

T(n, n-2) =
i=0..n-3 C(i-1, 0) ( 1 + 1/2 (n-i) (n2-n-2 i n+i+i2+6) ) +
i=0..n-3 C(i, 1) ( 1 + (n-i)(n-i+1) ) +
i=0..n-3 C(i+1, 2) ( 1 + 2(n-i) ) +
j=0..2 C(n+j-3, j) × 11

Y luego los exteriores:

T(n, n-2) =
1/24 (n-2)(3n3+8n2+49n+156) +
1/12 (n-3)(n-2)(n2+7n+34) +
1/12 (n-3)(n-2)(n-1)(n+10) +
11/2 (n-1)n

con lo que llegamos al término final:

T(n, n-2) = 1/24 (7 n4 + 14 n3 + 77 n2 - 122 n - 24)

Al expandirlo obtenemos la secuencia para n≥2 ⇒ T(n, n-2) = 11, 52, 142, 309, 588, 1021, 1657, 2552, ... tal como obtuvimos en la tabla de valores resaltados en azul.

Deduciendo el término general

Repitiendo aquí las soluciones que hemos visto para b=0

T(n, n) =
i=0..n-1 C(i-1, 0) ( 1 + ∑j=n-i..n-i ( (n-i) +!0 (n-i) C(2(n-i)-j-1, (n-i)-1)) +
j=0..0 C(n+j-1, j) × Td(0)

para b=1

T(n, n-1) =
i=0..n-2 C(i-1, 0) ( 1 + ∑j=n-i-1..n-i ( (n-i) + !1 (n-i) C(2(n-i)-j-2, (n-i)-2) )) +
i=0..n-2 C(i, 1) ( 1 + ∑j=n-i..n-i ( (n-i) + !1 (n-i) C(2(n-i)-j-2, (n-i)-2) )) +
j=0..1 C(n+j-2, j) × Td(1)

y para b=2

T(n, n-2) =
i=0..n-3 C(i-1, 0) ( 1 + ∑j=n-i-2..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
i=0..n-3 C(i, 1) ( 1 + ∑j=n-i-1..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
i=0..n-3 C(i+1, 2) ( 1 + ∑j=n-i..n-i ( (n-i) + !2 (n-i) C(2(n-i)-j-3, (n-i)-3) )) +
j=0..2 C(n+j-3, j) × Td(2

podemos entonces suponer que para un término génerico b tenemos este despliegue:

T(n, n-b) =
i=0..n-(b+1) C(i-1, 0) ( 1 + ∑j=n-i-(b-0)..n-i ( (n-i) + !b (n-i) C(2(n-i)-j-(b+1), (n-i)-(b+1)) )) +
i=0..n-(b+1) C(i, 1) ( 1 + ∑j=n-i-(b-1)..n-i ( (n-i) + !b (n-i) C(2(n-i)-j-(b+1), (n-i)-(b+1)) )) +
i=0..n-(b+1) C(i+1, 2) ( 1 + ∑j=n-i-(b-2)..n-i ( (n-i) + !b (n-i) C(2(n-i)-j-(b+1), (n-i)-(b+1)) )) +
···
i=0..n-(b+1) C(i+b-1, b) ( 1 + ∑j=n-i..n-i ( (n-i) + !b (n-i) C(2(n-i)-j-(b+1), (n-i)-(b+1)) )) +
j=0..b C(n+j-(b+1), j) × Td(b)

Sumando

T(n, n-b) = ∑h=0..b (i=0..n-(b+1) C(i+h-1, h) ×
( 1 +∑j=n-i-(b-h)..n-i ( (n-i) + !b (n-i) C(2(n-i)-j-(b+1), (n-i)-(b+1)) )))+
j=0..b C(n+j-(b+1), j) × Td(b)

Ahora deshacemos el cambio b = n - k

T(n, k) = h=0..n-k i=0..k-1 ( C(i+h-1, h) ×
( 1 + ∑j=k-i+h..n-i ((n-i) + !(n-k) (n-i) C(n+k-j-2i-1, k-i-1)) )) +
j=0..n-k C(j+k-1, k-i-1) Td(n-k)

Finalmente hacemos un ajuste con Max() para los casos cuando n<k, en cuyo caso el coste es uno, llegando a la fórmula final que coincide con el contaje de iteraciones en el algoritmo para generar los desarreglos parciales:

T(n, k) = Max(1, h=0..n-k i=0..k-1 ( C(i+h-1, h) ×
( 1 + ∑j=k-i+h..n-i ((n-i) + !(n-k) (n-i) C(n+k-j-2i-1, k-i-1)) )) +
j=0..n-k C(j+k-1, k-i-1) Td(n-k))

Implementamos el cálculo de esta extensa fórmula con este código en la herramienta:

valueCost: function(n, k) {
    let b = n-k;
    let sf = subfactorial(b);
    let value = 0;
    for (let h=0; h<=b; h++) {
        for (let i=0; i<=k-1; i++) {
            let val = 1;
            for (let j=k-i+h; j<=n-i; j++) {
                val += (n-i) + sf * (n-i) * binomial(n+k-j-2*i-1, k-i-1);
            }
            value += binomial(i+h-1, h) * val;
        }
    }
    let val = 0;
    for (let j=0; j<=b; j++) {
        val += binomial1(j+k-1, j);
    }
    value += val * codesFunc.derange1.valueCost(b)[0];
    return [Math.max(1, value)];
}

Dada la complejidad del despliegue, creo que no será fácil aplicar la técnica de la función generadora, por lo que nos quedaremos con lo obtenido.

Comparando costes de algoritmos para generar desarreglos parciales

Comparamos los tres algoritmos implementados para generar desarreglos parciales

En la siguiente tabla comparamos costes para k=3:

nT(n, 3)
derangeP1(n, 3)derangeP2(n, 3)derangeP3(n, 3)
0611
1711
2811
3319918
42921447
52991762309
6124092641549
7103857395712046
889872647098105111
989703564474881048811
1098373142131385811527529

Para n≥3 el que tiene menor coste es derangeP1, pues no es un recursivo directo, usando combine2() y derange1() en un bucle que componía los resultados. En cambio derangeP2 y derangeP3 si son recursivos directos, usando un algoritmo de recorte de listas. derangeP2 es más costoso debido a que había que remover duplicados.