Dados los nnúmeros en una matriz (no se puede suponer que son enteros), me gustaría calcular el producto de todos los subconjuntos de tamaño n-1.
Puede hacerlo multiplicando todos los números y luego dividiéndolos por turnos, siempre que ninguno de los números sea cero. Sin embargo, ¿qué tan rápido puedes hacer esto sin división?
Si no permite la división, ¿cuál es el número mínimo de operaciones aritméticas (por ejemplo, multiplicación y suma) necesarias para calcular el producto de todos los subconjuntos de tamaño n-1?
Claramente puedes hacerlo en (n-1)*nmultiplicaciones.
Para aclarar, la salida son nproductos diferentes y las únicas operaciones aparte de leer y escribir en la memoria permitidas son la multiplicación, la suma y la resta.
Ejemplo
Si la entrada tiene tres números 2,3,5, entonces la salida es tres números 15 = 3*5, 10 = 2*5y 6 = 2*3.
Criterio ganador
Las respuestas deben dar una fórmula exacta para la cantidad de operaciones aritméticas que usará su código en términos de n. Para simplificar la vida, simplemente conectaré n = 1000tu fórmula para juzgar su puntaje. Cuanto más bajo, mejor.
Si es demasiado difícil producir una fórmula exacta para su código, puede ejecutarla n = 1000y contar las operaciones aritméticas en el código. Sin embargo, una fórmula exacta sería la mejor.
Debe agregar su puntaje n=1000a su respuesta para una comparación fácil.
fuente

+en índices ? Si este es el caso, ¿también cuenta la indexación de matrices? (dado que, después de todo, es el azúcar sintáctico para sumar y desreferenciar).(n-1)*nmultiplicaciones ¿ Quieres decir(n-2)*n, verdad?Respuestas:
Python, 3 (n-2) operaciones, puntaje = 2994
Las matrices
leftyrightcontienen los productos acumulados de la matriz desde la izquierda y desde la derecha, respectivamente.EDITAR: Prueba de que 3 (n-2) es el número óptimo de operaciones necesarias para n> = 2, si solo usamos la multiplicación.
Lo haremos por inducción; según el algoritmo anterior, solo tenemos que demostrar que para n> = 2, 3 (n-2) es un límite inferior en el número de multiplicaciones necesarias.
Para n = 2, necesitamos al menos 0 = 3 (2-2) multiplicaciones, por lo que el resultado es trivial.
Supongamos que n> 2, y supongamos que para n - 1 elementos, necesitamos al menos 3 (n-3) multiplicaciones. Considere una solución para n elementos con k multiplicaciones. Ahora, eliminamos el último de estos elementos como si fuera 1, y simplificamos todas las multiplicaciones directamente por él. También eliminamos la multiplicación que conduce al producto de todos los demás elementos, ya que ese no es necesario, ya que nunca se puede utilizar como un valor intermedio para obtener el producto de n-2 de los otros elementos, ya que la división no está permitida. Esto nos deja con l multiplicaciones, y una solución para n - 1 elementos.
Por hipótesis de inducción, tenemos l> = 3 (n-3).
Ahora, echemos un vistazo a cuántas multiplicaciones se eliminaron. Uno de ellos fue el que condujo al producto de todos los elementos, excepto el último. Además, el último elemento se usó directamente en al menos dos multiplicaciones: si se usó en una sola, se usó al multiplicar por un resultado intermedio que consiste en algún producto de los otros elementos; digamos, sin pérdida de generalidad, este resultado intermedio incluye el primer elemento en el producto. Entonces, no hay forma de obtener el producto de todos los elementos, excepto el primero, ya que cada producto que contiene el último elemento es el último o contiene el primer elemento.
Por lo tanto, tenemos k> = l + 3> = 3 (n-2), lo que demuestra el teorema reivindicado.
fuente
f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l).Haskell , puntuación 2994
Pruébalo en línea!
Digamos que nos dan la lista
[a,b,c,d,e,f,g,h]. Primero lo agrupamos en pares[[a,b],[c,d],[e,f],[g,h]]. Luego, recurrimos a la listapairsde tamaño medio de sus productos para obtenersubresultsSi tomamos el primer elemento
(c*d)*(e*f)*(g*h)y lo multiplicamos porbyarespectivamente, obtenemos el producto de todo peroay todo menosb. Al hacer esto para cada par y resultado recursivo con ese par perdido, obtenemos el resultado final. El caso de longitud impar se maneja especialmente haciendo pasar el elemento impar sin emparejar al paso recursivo, y el producto de los elementos restantes devueltos es el producto sin él.El número de multiplicaciones
t(n)esn/2para el producto de emparejamiento,t(n/2)para la llamada recursiva y otronpara los productos con elementos individuales. Esto dat(n) = 1.5 * n + t(n/2)por extrañon. El uso de recuentos más precisos para lanmultiplicación impar e ignorando1para el caso base da puntaje2997paran=1000.fuente
products_but_one'podría evitar eso al devolver algo de la longitud correcta.1multiplicación gratuita. Creo que el relleno 1 no afectó las cosas, pero limpié mi algoritmo para no usarlas.float.Haskell , puntaje 9974
Pruébalo en línea!
Una estrategia de divide y vencerás, que recuerda mucho al tipo de fusión. No hace ninguna indexación.
La función
partitiondivide la lista en mitades tan iguales como sea posible al colocar elementos alternos en lados opuestos de la partición. Fusionamos recursivamente los resultados(p,r)para cada una de las mitades, conrla lista de productos con uno perdido ypel producto general.Para la salida de la lista completa, el elemento que falta debe estar en una de las mitades. El producto que falta ese elemento es un producto que falta para la mitad en el que está, multiplicado por el producto completo para la otra mitad. Entonces, multiplicamos cada producto con uno perdido por el producto completo de la otra mitad y hacemos una lista de los resultados, como
map(*p1)r2 ++ map(*p2)r1). Esto tomanmultiplicaciones, dondenes la longitud. También tenemos que hacer un nuevo producto completop1*p2para uso futuro, que cuesta 1 multiplicación más.Esto le da a la recursión general para el número de operaciones
t(n)connaún:t(n) = n + 1 + 2 * t(n/2). El impar es similar, pero una de las sublistas es1más grande. Al hacer la recursión, obtenemosn*(log_2(n) + 1)multiplicaciones, aunque la distinción par / impar afecta ese valor exacto. Los valores hastat(3)se mejoran al no multiplicar por1definiendo una variante(%)de los(*)accesos directos a los casos_*1o1*_.Esto da
9975multiplicaciones paran=1000. Creo que la pereza de Haskell significa que no se calcula el producto general no utilizado en la capa externa9974; si me equivoco, podría omitirlo explícitamente.fuente
n = 1000y contar las operaciones aritméticas en el código.9974y no9975multiplicacionesn = 1000(en el caso de calcular el producto general en la capa externa). ¿Incluiste un1en la entrada que usaste para probarlo?tracea partirDebug.Tracede un cajón de sastre| trace "call!" False = undefinedguardia, creo. Pero esto se usaunsafePerformIOdebajo del capó, por lo que no es realmente una gran mejora.Haskell , puntuación 2994
Pruébalo en línea!
Cómo funciona
Esta es una versión limpia del algoritmo de xnor que trata el caso extraño de una manera más directa (editar: parece que xnor lo ha limpiado de la misma manera):
[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] por recursión ↦
[(bc) (de) (fg), a (de) (fg) c, a (de) (fg) b, a (bc) (fg) e, a (bc) (fg) d, a (bc) (de) g, a (bc) (de) f]
[a, b, c, d, e, f, g, h] ↦
[ab, cd, ef, gh] ↦
[(cd) (ef) (gh), (ab) (ef) (gh), ( ab) (cd) (gh), (ab) (cd) (ef)] por recursión ↦
[(cd) (ef) (gh) b, (cd) (ef) (gh) a, (ab) (ef ) (gh) d, (ab) (ef) (gh) c, (ab) (cd) (gh) f, (ab) (cd) (gh) e, (ab) (cd) (ef) h, (ab) (cd) (ef) g].
fuente
O (n log n) operaciones, puntaje = 9974
Funciona con un árbol binario.
Pitón
Esto también requiere operaciones de suma de listas y algo de aritmética en números que no son los valores de entrada; No estoy seguro si eso cuenta. La
mulfunción está ahí para guardar n operaciones para el caso base, para evitar desperdiciarlas multiplicando por 1. En cualquier caso, se trata de operaciones O (n log n). La fórmula exacta es, si solamente contando operaciones aritméticas sobre números de entrada, conj = floor(log_2(n)):j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2.¡Gracias a @xnor por guardar una operación con la idea de no calcular el producto externo!
La última parte es generar los productos en el orden del término faltante.
fuente
n = 1000y contar las operaciones aritméticas en el código.p[i] = p[i + i] * p[i + i+1]no se cuentan log2 n + noperaciones (que es O (nlogn) por ciertop[i] = p[i + i] * p[i + i + 1]deben ser guardadas por la optimización de multiplicación. Sin embargo, podría haber contado uno demasiado.O ((n-2) * n) = O (n 2 ): solución trivial
Esta es solo la solución trivial que multiplica cada uno de los subconjuntos:
Pitón
Tenga en cuenta que esto también requiere
noperaciones de adición de lista; No estoy seguro si eso cuenta. Si eso no está permitido,product(array[:index] + array[index + 1:])puede reemplazarse porproduct(array[:index]) * product(array[index + 1:]), lo que cambia la fórmula aO((n-1)*n).fuente
productfunciónO(n)? uno para cada elemento de la matriz (aunque esto se puede cambiar fácilmente aO(n-1))Python, 7540
Una estrategia de fusión tripartita. Creo que puedo hacerlo incluso mejor que esto, con una fusión aún grande. Es O (n log n).
EDITAR: se corrigió un error.
La función relevante es
missing_products, que proporciona el producto general y todos los que tienen un elemento faltante.fuente
tri_merge? También puede reemplazar el2 * split_size + ...intri_partitionconsplit_size + split_size + ....dc, puntaje 2994
Supongo que la comparación de enteros
z1=(que termina la recursión cuando alcanzamos el último valor) es libre. Esto es equivalente a los gustos deforeachen otros idiomas.Manifestaciones
Una demostración con entradas grandes y pequeñas:
fuente
C ++, puntaje: 5990, O ([2NlogN] / 3)
Esta implementación utiliza una tabla de búsqueda de árbol binario. Mi primera implementación fue O (NlogN), pero una optimización de último minuto, que busca el producto de todos los elementos de la matriz menos un par, + 2 multiplicaciones salvaron el día. Creo que esto aún podría optimizarse un poco más, tal vez otro 16% ...
He dejado algunos rastros de depuración, solo porque es más fácil eliminarlos que reescribirlos :)
[Editar] la complejidad real se mide en O ([2NlogN] / 3) para 100. En realidad, es un poco peor que O (NlogN) para conjuntos pequeños, pero tiende a O ([NlogN] / 2) a medida que la matriz crece O muy grande (0.57.NlogN) para un conjunto de 1 millón de elementos.
Estoy agregando el algoritmo de @nore, para completar. Es realmente agradable y es el más rápido.
fuente