lunes, 12 de octubre de 2026

Regresos 16 (3): Particiones condicionadas con PARI

En la anterior entrada se desarrolló un ejemplo de particiones condicionadas con Cartesius y un Buscador. Consistía en descomponer el número 50 en sumandos cuadrados, sin importar el número de ellos que usemos, pero que no pasara de 3. Las soluciones resultaron ser 50=25+16+9=25+25=49+1

Ahora repetiremos el ejemplo con las herramientas del lenguaje PARI.

En PARI se cuenta con la función FORPART, que recorre una partición cualquiera, en la que se pueden integrar condicionantes. Para el ejemplo actual valdría este código:

a(n) = {nb = 0; forpart(p=n, if (#p && (#select(x->issquare(x), Vec(p)) == #p), nb+=1;print(p)), , 3);nb; }
print(a(50))

Cuesta entender todo lo escrito, pero consiste en iniciar un contador nb, recorrer todas las particiones con forpart y condicionarlas con x->issquare(x). El 3 que aparece es el tope para el número de elementos de la partición. Finaliza incrementando el contador e imprimiendo resultados. Así queda en la página oficial de PARI:

 


Coincide con las anteriores herramientas

Segundo ejemplo

Encontrar el número de particiones de un número en números primos sin otro condicionamiento. Por ejemplo, con el número 20:

Utilizamos el código PARI siguiente:

a(n) = {nb = 0; forpart(p=n, if (#p && (#select(x->isprime(x), Vec(p)) == #p), nb+=1), , ); nb; }
print(a(20))

Lo probamos y resulta que son 26:


Si deseamos ver las 26 particiones, le añadimos un print(p) detrás de nb+=1. Observa la imagen:


Con el Buscador se puede comprobar. Basta corregir las líneas de filtro, usando ESPRIMO en lugar de ESCUAD. Se omiten los detalles, ya explicados en la anterior entrada.


Se ha usado como tope el 10, que es máximo número posible de sumandos primos.

Nos puede interesar aplicar otra condición. En el ejemplo anterior se podría exigir que los primos fueran pitagóricos, del tipo 4k+1. Resultaría:


Se ha añadido la condición x%4==1 (el signo % significa MÓDULO)

 Condiciones más exigentes

Partición con sumandos coprimos

He encontrado este ejemplo en https://oeis.org/A000837, y lo uso para resaltar la economía de medios del lenguaje PARI. He adaptado el código a este documento:

s=0;forpart(X=20, s+=(gcd(X)==1);print(s))

Con esta simple línea encontramos que existen 582 particiones en las que el conjunto de sumandos posee MCD igual a 1 (no se afirma que sean primos dos a dos). Aquí, la condición fundamental es gcd(X)==1

Función Q(n)

Se denomina así al número de particiones de un número en elementos distintos. Recordamos aquí que estas particiones son sencillas de encontrar con Cartesius. Basta usar la condición NO REPITE.

Estas son las condiciones para las particiones del número 10 en sumandos distintos:

xrango=10

xt=1..10

no repite

suma=10

creciente

Su solución es


Son diez en total, pero el proceso resulta lento e inestable.

En OEIS (https://oeis.org/A000009) se incluye este código PARI:

{a(n) = my(c); forpart(p=n, if( n<1 || p[1]<2, c++; for(i=1, #p-1, if( p[i+1] > p[i]+1, c--; break)))); c}; /* Michael Somos, Aug 13 2017 */

Es algo oscuro a la hora de entenderlo, pero funciona:

En el elemento correspondiente al 10 observamos que Q(10)=10, tal como resultó en Cartesius.

Con el Buscador

Anulamos los filtros o restricciones, pero en el momento de llegar a la suma de la partición añadimos esta condición:

vale = True

If pp > 1 Then

For j = 2 To pp

For k = 1 To j - 1

If su(e(j)) = su(e(k)) Then vale = False

Next k

Next j

End If

 

If vale Then

De esa forma, sólo nos devolverá las particiones con elementos distintos:


Según un importante teorema ya visto en este blog, el número de estas particiones coincide con las conseguidas si se restringen los sumandos a ser impares.

En el ejemplo del 10 nos resultarían, como era de esperar, 10 particiones con este código:

a(n) = {nb = 0; forpart(p=n, if (#p && (#select(x->x%2==1, Vec(p)) == #p), nb+=1;print(p)), , 3);nb; }
print(a(10))


Con Cartesius bastarían las condiciones

xrango=10

xt=1..10

xt=filtro(impar)

suma=10

creciente

No es recomendable por su lentitud.

Sin más explicaciones, este sería el resultado si eliminamos los sumandos pares:

 



Particiones con un número par o impar de sumandos

En estos ejemplos usaré PARI:

Con un número par:

Es importante la parte #p%2==0,nb+=1, pues % es la operación MODULO y nos indica que la partición tiene un número par de elementos #p.

a(n) = {nb = 0; forpart(p=n, if(#p%2==0,nb+=1), , ); nb; }
print(a(20))

Nos resulta que existen 317 particiones con un número par de elementos. Si deseamos datos de los primeros números obtendremos:


Coinciden con los publicados en
https://oeis.org/A027187

Particiones con número impar de elementos:

Bastaría cambiar #p%2==0 por #p%2==1:


Publicados en
https://oeis.org/A027193

Podemos usar ambas situaciones en funciones separadas, y experimentar algo con ellas. Por ejemplo, que las de número par sean dos más que las de impar:

a(n) = {nb = 0; forpart(p=n, if(#p%2==0,nb+=1), , ); nb; }

b(n) = {nb = 0; forpart(p=n, if(#p%2==1,nb+=1), , ); nb; }

for(i=1,100, if(a(i)==b(i)+2,print1(i,”, “)))


No tiene mucho interés, porque en los primeros números solo se cumple en el 8 y el 10.

También se pueden contar por separado las particiones de cardinal par y las de impar en la misma función. La siguiente busca si el número de particiones de orden par es dos unidades mayor que las de impar en un mismo número n.

c(n) = {my(nb = 0,na=0);forpart(p=n, if(#p%2==0,nb+=1,na+=1,), , ); nb==na+2 }
for(i=1,30, if(c(i)==1,print1(i,”, “)))

Escasean también los resultados.

Lo dejamos aquí. Ya se ha abierto una puerta a más experimentaciones.

Unos detalles para finalizar

PARI posee la función numbpart, que cuenta el número de elementos de una partición. Este código elabora un listado de los números con un número par de particiones distintas:

a(n)={my(c);c=numbpart(n);if(c%2==0,return(c))}
for(i=1,50,if(a(i)>0,print1(i,", ")))


Particiones con producto acotado

En la primera parte de la instrucción forpart se puede incluir toda una línea de código. Esto permite calcular productos en las particiones y condicionarlos.

Por ejemplo, lo siguiente encuentra aquellas particiones de n=5 cuyo producto es mayor que 3. Es importante destacar la forma en la que hay que usar las distintas partes de una partición con Vec(p)[k]):

a(n) = { nn=0; forpart(p=n, pp=1;for(k=1,#p,pp=pp*Vec(p)[k]);if(pp>3,nn+=1;print(p);p));nn }
print(a(5))

Resultado:


Otro ejemplo: podemos reproducir el contenido de
https://oeis.org/A319005

Number of integer partitions of n whose product of parts is >= n.

 1, 1, 1, 1, 2, 2, 5, 7, 13, 18, 28, 40, 60, 80, 113, 152, 205, 266, 353, 454, 590, 751, 959, 1210, 1529, 1905, 2381, 2953, 3658, 4501, …

Usaremos

a(n) = {nn=0; forpart(p=n, pp=1;for(k=1,#p,pp=pp*Vec(p)[k]);if(pp>=n,nn+=1));nn }
for(i=1,30,print1(a(i),", "))

Resultado:



Con estos ejemplos se reconoce la enorme capacidad de PARI para manejar particiones. Paramos ejemplos por ahora.

viernes, 2 de octubre de 2026

Regresos 16 (2): Particiones condicionadas. Algoritmo básico

En esta entrada se desarrollan las ideas sobre punteros móviles para evitar el uso de múltiples bucles en Combinatoria. Seguiremos actuando sobre particiones de un número.

Un ejemplo previo

Supongamos que deseo descomponer el número 50 en sumandos cuadrados, sin importarme el número de ellos que usemos, pero que sea a lo más tres.

Con Cartesius

Esta herramienta no es excesivamente amigable, pero la suelo incluir por sus prestaciones. En ella podríamos plantear lo siguiente:

xrango=3

xt=1..50

xt=filtro(cuadrado)

creciente

suma=50

 

Estas condiciones, por orden, exigen que los sumandos sean uno, dos o tres. Después se fijan los datos en el conjunto 1…50. Se les aplica un filtro para quedarnos tan solo con los cuadrados, y, finalmente, se fija la suma en 50 con sumandos crecientes.


El resultado es




Cartesius solo llega a conjuntos de 12, luego necesitaremos, si deseamos trabajar con Excel, otra herramienta distinta. Es lo que voy a presentar ahora, para destacar la conveniencia del uso de punteros. En negrita y cursiva figura el código, y en tipo normal los comentarios.

Con el Buscador

Este código de algoritmo puede resultar pesado o difícil, por lo que quienes sólo deseen estudiar los ejemplos lo pueden ignorar. Es interesante porque permite, con ligeros retoques, su adaptación a muchas situaciones.

Sub particion()

'Devuelve el número total de particiones condicionadas y su desarrollo

Dim pp, i, j, contador, tope, fila, n, final, suma

Dim e(1000), su(1000)

Dim seguir As Boolean

Dim s$ ‘Contenedor de particiones

 

‘Lee el número y el tope de las particiones

 

n = ActiveWorkbook.Sheets(3).Cells(4, 7).Value

tope = ActiveWorkbook.Sheets(3).Cells(5, 7).Value

 

Call borrarango("e6:i2000")

fila = 8

final = n

For i = 1 To n 'Inicios

 e(i) = 0

 su(i) = 0

 su(i) = i

Next i

 

fila = 8 ‘Inicio de la presentación de resultados

For i = 1 To n 'Inicios

 e(i) = 0 ‘Este vector sigue a los sumandos en la tabla

su(i) = 0, Contenido de la celda que señala el puntero

Next i

 

'Restricciones o filtros En el ejemplo, que sean cuadrados

i = 1

While i <= final

If Not escuad(su(i)) Then

For j = i To n - 1: su(j) = su(j + 1): Next j

final = final – 1 ‘Elimina los que no valen

Else

i = i + 1

End If

Wend

If Not escuad(su(final)) Then final = final – 1

‘Final es el máximo número de sumandos

contador = 0

 

pp = 1  'inicio de puntero

'Aquí inicios varios según el problema a tratar

suma = 0

While pp > 0 'puntero activo((va marcha atrás en ese paso)

‘El puntero va y viene mientras sea positivo y no sobrepase los topes

 

e(pp) = e(pp) + 1 ' avanza en columna dentro del puntero

suma = suma + su(e(pp))

 

seguir = False 'determina si se sigue o no en la misma columna de datos

 

If e(pp) > final Or suma > n Or (pp > 1 And e(pp) > e(pp - 1)) Then seguir = True 'Se ha llegado al tope del rango de datos, y hay que seguir con otra columna

 

If seguir Then 'Seguir significa retroceder el puntero e iniciar su contenido

e(pp) = 0:  pp = pp - 1: suma = 0

 

Else

'Hay algo que comunicar sobre el conjunto, quizás una solución, un mensaje o un añadido

suma = 0

For j = 1 To pp: suma = suma + su(e(j)): Next j

If suma = n And pp <= tope Then

contador = contador + 1’Una solución

fila = fila + 1

s = ""

For j = 1 To pp: s = s + Str$(su(e(j))): Next j

ActiveWorkbook.Sheets(3).Cells(fila, 6).Value = s

End If

If pp < n Then pp = pp + 1

   

End If 'Nivel 1

If pp < n Then pp = pp + 1

End If

Wend

 

'Operaciones finales

ActiveWorkbook.Sheets(3).Cells(5, 5).Value = contador

End Sub

El ejemplo actual se resuelve cambiando las restricciones (en este caso escuad()), porque deseamos que los sumandos sean cuadrados. El resto, valor de n y del tope, se leen en el Buscador.

Al pulsar en el botón de búsqueda obtenemos la solución deseada:

 


Abajo aparecerán las soluciones y, a la izquierda del tope, el número de ellas, que aquí son 3.

La ventaja de este algoritmo es que puede devolver particiones superiores a 12, que es la limitación de Cartesius. También es más rápido.

En la imagen podemos observar las particiones de 100 en trece elementos que son cuadrados pares:


Han resultado trece particiones.

En la siguiente entrada acudiremos mayoritariamente a la instrucción de PARI forpart.

jueves, 24 de septiembre de 2026

Regresos 16 (1): Particiones condicionadas de un número natural

En el año 2017 publiqué en mi blog “Números y hoja de cálculo” (https://hojaynumeros.blogspot.com/) una serie de estudios sobre particiones de un número obtenidas mediante mi herramienta Cartesius.

Quedé bastante satisfecho, pero echaba de menos poder estudiar las particiones de un número (formas de descomponer un número natural en sumandos) para valores mayores que doce, que es el máximo que me permitía Cartesius. He despojado los algoritmos de esta herramienta de muchas opciones, y me he quedado con lo imprescindible para calcular números de particiones condicionadas. Sigue siendo un proceso lento, pero me permite, con paciencia, abordar cálculos para números mayores que doce.

Lo he incorporado a la hoja “Buscador”, que tengo preparada para su descarga gratuita (https://www.hojamat.es/blog/buscador1.xlsm), y con ella, y sin abandonar Cartesius, ampliaré los trabajos del año 2017.

Tres herramientas

Esta va a ser una serie de entradas, por lo que es conveniente realizar una presentación previa de las mismas. En primer lugar, hay que advertir que el espíritu de ellas es el de aprendizaje y ejercitación. En este tema está todo publicado, y desde este blog poco se puede añadir, pero sí resulta atractivo cotejar distintos métodos para llegar a un mismo objetivo.  El mejor y más aconsejable sería el de trabajar con papel y bolígrafo, que es como mejor se aprende, pero al final se terminará recurriendo a herramientas informáticas.

Usaremos, de forma más o menos simultánea, tres herramientas básicas:

Cartesius

Es mi herramienta tradicional para construir productos cartesianos condicionados. En el tema de particiones ya he publicado muchos ejemplos, y resulta bastante sencilla de entender. Sus condiciones resultan claras.

Descargable desde

https://www.hojamat.es/sindecimales/combinatoria/herramientas/herrcomb.htm#cartesius

Buscador

En el archivo descargable presentado más arriba he situado, en su tercera hoja, una subrutina para realizar el mismo trabajo que el de Cartesius, pero que no sufre la restricción de las doce columnas. Resulta también más rápida, ya que el que no sea tan general le dota de más velocidad, pero nunca suficiente, porque todos los temas de Combinatoria acaban por abordar cálculos que resultan muy lentos.

Dirección de descarga: https://www.hojamat.es/blog/buscador1.xlsm

PARI

Este lenguaje posee herramientas potentes y rápidas, aunque adolece de falta de presentaciones intuitivas. Está dirigido a profesionales, pero en este blog le hemos conseguido mucha utilidad. Su orden FORPART será un pequeño tesoro para comprobar cálculos o ampliarlos.

Página web: https://pari.math.u-bordeaux.fr/gpwasm.html

 Algoritmo básico

Las particiones actúan sobre conjuntos de sumandos. Por ejemplo, si deseo descomponer el número 20 en cuatro sumandos primos, deberé usar el conjunto (2, 3, 5, 7, 11, 13, 17, 19). El resultado final es que existen 6 particiones distintas.

 Particiones condicionadas                         

Número:    20             

Número de particiones: 6

Conjunto de particiones: 5 5 5 5, 7 5 5 3, 7 7 3 3, 11 3 3 3, 11 5 2 2,       13 3 2 2

En algunas ocasiones, el número de particiones condicionadas proviene de operaciones sobre conjuntos de números sometidos a alguna operación (condición previa). Por ejemplo, podemos estar interesados en particiones en las que las partes sean todas libres de cuadrados, o que pertenezcan a la sucesión de Fibonacci. Esta será la condición previa en cada caso.

El manejo de cuatro números que no tienen que guardar relación entre ellos nos lleva, en el nivel elemental de algoritmos en el que se mueve esta serie, a usar bucles anidados: FOR I=12 T0…FOR J=12 TO…FOR K=12 TO… Esto supone una gran ineficiencia y poca versatilidad, porque el número de bucles debería adaptarse a cada ejemplo determinado.

Desde hace muchos años llevo usando punteros sobre una matriz rectangular (es una imagen para entendernos) en el que van avanzando por los distintos elementos llevando algún tipo de recuerdo de los que ya han visitado.

Concretando el ejemplo del primer párrafo, imaginemos que estudiamos el rango de números entre 40 y 50, y buscamos en ellos conjuntos de cuatro números con la propiedad pedida. Llamamos, por ejemplo, INICIO al valor mínimo (40 en este caso) y TOPE o FINAL al último (50). Por otra parte, daremos el nombre de NUME al número de elementos que tendrán nuestros conjuntos. Desde INICIO hasta final llamaremos E(I) a cada valor concreto de un elemento. En la imagen que sigue sería una columna, mientras que horizontalmente se representarían los elementos elegidos de izquierda a derecha, que será el “movimiento” que tendrá el puntero.

 


Al principio cuesta un poco entender la dinámica de este proceso, pero compensa el no tener que anidar bucles. En mis herramientas de ayuda a la enseñanza lo he usado, entre otros, en estas tres:

Combimaq (https://www.hojamat.es/sindecimales/combinatoria/herramientas/herrcomb.htm#combimaq)

Cartesius

(https://www.hojamat.es/sindecimales/combinatoria/herramientas/herrcomb.htm#cartesius)

Partlista (https://www.hojamat.es/sindecimales/aritmetica/herramientas/herrarit.htm#reprenum)

En la siguiente entrada se desarrollarán estas ideas.

 

lunes, 14 de septiembre de 2026

Submulticonjuntos (2)

 Representación numérica de un multiconjunto

Con lo visto hasta ahora en la anterior entrada, lo esencial en un multiconjunto lo constituyen las multiplicidades. La naturaleza de los elementos no influye apenas en los conteos. Es un fenómeno similar a la descomposición en factores primos, que muchas funciones dependen tan solo de los exponentes. Esto nos permite representar un multiconjunto como un número, de forma unívoca. De las posibilidades existentes (hay variantes en la tarea de representar un multiconjunto mediante un número, como, por ejemplo, los números de Heinz), elegiremos aquí las siguientes:

·      Ordenamos las multiplicidades de mayor a menor (para que el número resultante sea lo más pequeño posible)

·      A cada multiplicidad correspondiente a un elemento dado le asignamos un número primo repetido, siguiendo el orden natural (algunos autores comienzan con el 1). En caso de empate, no hay problema, porque resulta el mismo número.

·   Si multiplicamos todos esos primos, resultará un número que poseerá una relación biunívoca con el conjunto.

Por ejemplo, en la palabra ZARAGOZA ordenamos las multiplicidades vistas en la entrada anterior: 3 de A, 2 de Z y 1 del resto de caracteres, R, G y O.

Sustituimos cada multiplicidad por números primos repetidos y ordenados:

2*2*2*3*3*5*7*11=27720.

Este número identifica, de forma unívoca, a todos los multiconjuntos con la serie de multiplicidades 3, 2, 1, 1, 1. Cada submulticonjunto de ellos se relacionará con un divisor de 27720, por el criterio general de divisibilidad, en el que los factores primos de los divisores son subconjuntos de los correspondientes en 27720.

Pedimos los divisores de 27720 con bigomega=3 (tres factores primos) en cualquier buscador, o con la función siguiente:

Function dosfact$(n, k)

Dim i

s = ""

For i = 2 To n

If n / i = n \ i Then

If bigomega(i) = k Then

s = s + ajusta(i) + "  "

End If

End If

Next i

dosfact = s

End Function

La aplicamos a 27720 con k=3, por ejemplo, y resultan 19 divisores:

8  12  18  20  28  30  42  44  45  63  66  70  99  105  110  154  165  231  385 

Ahora usamos la técnica de las combinaciones acotadas que vimos en la entrada anterior, con Cartesius:

xtotal=3

xt=2,3,5,7,11

creciente

contar(2)<4

contar(3)<3

contar(5)<2

contar(7)<2

contar(11)<2

Coincide el número de submulticonjuntos de tres elementos:

Son también 19.

Cálculo directo

Usando la técnica combinatoria de eliminar combinaciones prohibidas resultaría:

Combinaciones con repetición:

COMBINAT(5+3-1;3) = COMBINAT(7;3 = 35

Hay que restarle uno por la combinación prohibida (3,3,3) y para el 5, 7 y 11, cinco cada uno para eliminar aquellos en los que están repetidos: (5,5,2), (5,5,3), …

En total: 35-1-5-5-5=19

Para casos más complicados habría que acudir a técnicas parecidas al Principio de Inclusión y exclusión, sumando y restando posibilidades. Para cálculos mediante fórmulas directas, las incluidas en https://arxiv.org/abs/2009.01233, m-submultisets and m-permutations of multisets elements

Para encontrar el número total de submulticonjuntos sustituiríamos XTOTAL=3 en Cartesius por XRANGO=8, que los abarca todos menos el conjunto vacío. Resultan 95.

Si contamos el conjunto vacío, serían 96, representado en los divisores de 27720 por el número 1.

Si ahora calculamos la función TAU (número de divisores) por su fórmula, usando las multiplicidades, resulta:

TAU(27720)=(1+3)(1+2)(1+1)(1+1)(1+1)=4*3*2*2*2=96

El paralelismo entre subconjuntos y divisores queda comprobado. Es interesante.