Algoritmo mais rápido para obter o produto de todos os subconjuntos

23

Dados os nnúmeros em uma matriz (você não pode assumir que sejam números inteiros), gostaria de calcular o produto de todos os subconjuntos de tamanho n-1.

Você pode fazer isso multiplicando todos os números e depois dividindo cada um por vez, desde que nenhum deles seja zero. No entanto, com que rapidez você pode fazer isso sem divisão?

Se você não permite a divisão, qual é o número mínimo de operações aritméticas (por exemplo, multiplicação e adição) necessárias para calcular o produto de todos os subconjuntos de tamanho n-1?

Claramente você pode fazê-lo em (n-1)*nmultiplicações.

Para esclarecer, a saída é de nprodutos diferentes e as únicas operações além da leitura e gravação na memória permitidas são multiplicação, adição e subtração.

Exemplo

Se a entrada tem três números 2,3,5, então a saída é de três números 15 = 3*5, 10 = 2*5e 6 = 2*3.

Critério de vitória

As respostas devem fornecer uma fórmula exata para o número de operações aritméticas que seu código usará em termos de n. Para simplificar a vida, apenas conectarei n = 1000sua fórmula para avaliar sua pontuação. Quanto menor, melhor.

Se for muito difícil produzir uma fórmula exata para o seu código, você pode executá-la n = 1000e contar as operações aritméticas no código. Uma fórmula exata seria melhor no entanto.

Você deve adicionar sua pontuação n=1000à sua resposta para facilitar a comparação.

Arthur
fonte
4
Podemos contar multiplicando por 1 como grátis? Caso contrário, eu definiria uma função de multiplicação personalizada que faz isso.
Xnor
3
Seria contra as regras fazer um monte de multiplicações em paralelo concatenando números juntamente com número suficiente de espaçadores 0?
Xnor
1
As operações, como +nos índices, contam? Se for esse o caso, a indexação de array também conta? (já que, afinal, é açúcar sintático para adição e desreferenciação).
nore 18/06
2
@ nore OK, desisto :) Basta contar as operações aritméticas que envolvem a entrada de alguma forma.
Arthur
1
Claramente você pode fazer isso em (n-1)*nmultiplicações. Você quer dizer (n-2)*n, certo?
Luis Mendo

Respostas:

25

Python, 3 (n-2) operações, score = 2994

l = list(map(float, input().split()))
n = len(l)

left = [0] * len(l)
right = [0] * len(l)
left[0] = l[0]
right[-1] = l[-1]
for i in range(1,len(l)-1):
  left[i] = l[i] * left[i - 1]
  right[-i-1] = l[-i-1] * right[-i]

result = [0] * len(l)
result[-1] = left[-2]
result[0] = right[1]
for i in range(1, len(l) - 1):
  result[i] = left[i - 1] * right[i+1]

print(result)

As matrizes lefte rightcontêm os produtos acumulados da matriz da esquerda e da direita, respectivamente.

EDIT: Prova de que 3 (n-2) é o número ideal de operações necessárias para n> = 2, se usarmos apenas a multiplicação.

Faremos isso por indução; pelo algoritmo acima, apenas temos que provar que para n> = 2, 3 (n-2) é um limite inferior ao número de multiplicações necessárias.

Para n = 2, precisamos de pelo menos 0 = 3 (2-2) multiplicações, portanto o resultado é trivial.

Seja n> 2 e suponha que para n - 1 elementos, precisamos de pelo menos 3 (n-3) multiplicações. Considere uma solução para n elementos com k multiplicações. Agora, removemos o último desses elementos como se fosse 1 e simplificamos todas as multiplicações diretamente por ele. Também removemos a multiplicação que leva ao produto de todos os outros elementos, pois esse não é necessário, pois nunca pode ser usado como um valor intermediário para obter o produto de n-2 dos outros elementos, pois a divisão não é permitida. Isso nos deixa com l multiplicações e uma solução para n - 1 elementos.

Pela hipótese de indução, temos l> = 3 (n-3).

Agora, vamos dar uma olhada em quantas multiplicações foram removidas. Um deles foi o que levou ao produto de todos os elementos, exceto o último. Além disso, o último elemento foi usado diretamente em pelo menos duas multiplicações: se foi usado em apenas uma, então foi usado quando multiplicado por um resultado intermediário que consistia em algum produto dos outros elementos; digamos, sem perda de generalidade, esse resultado intermediário incluiu o primeiro elemento no produto. Portanto, não há como obter o produto de todos os elementos, exceto o primeiro, pois todo produto que contém o último elemento é o último elemento ou contém o primeiro elemento.

Temos, portanto, k> = l + 3> = 3 (n-2), comprovando o teorema reivindicado.

nore
fonte
8
Isso acaba muito limpo em Haskell : f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l).
Xnor
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Dennis
12

Haskell , pontuação 2994

group :: Num a => [a] -> [[a]]
group (a:b:t) = [a,b] : group t
group [a] = [[a]]
group [] = []

(%) :: (Num a, Eq a) => a -> a -> a
a % 1 = a
1 % b = b
a % b = a * b

prod_one_or_two :: (Num a, Eq a) => [a] -> a
prod_one_or_two [a, b] = a % b
prod_one_or_two [x] = x

insert_new_value :: (Num a, Eq a) => ([a], a) -> [a]
insert_new_value ([a, b], c) = [c % b, c % a]
insert_new_value ([x], c) = [c]

products_but_one :: (Num a, Eq a) => [a] -> [a]
products_but_one [a] = [1]
products_but_one l = 
    do combination <- combinations ; insert_new_value combination
    where 
        pairs = group l
        subresults = products_but_one $ map prod_one_or_two pairs
        combinations = zip pairs subresults

Experimente online!

Digamos que recebemos a lista [a,b,c,d,e,f,g,h]. Primeiro, agrupamos em pares [[a,b],[c,d],[e,f],[g,h]]. Em seguida, recuamos na lista pairsde tamanho médio de seus produtos para obtersubresults

[a*b, c*d, e*f, g*h] -> [(c*d)*(e*f)*(g*h), (a*b)*(e*f)*(g*h), (a*b)*(c*d)*(g*h), (a*b)*(c*d)*(e*f)]

Se pegarmos o primeiro elemento (c*d)*(e*f)*(g*h)e multiplicá-lo por be arespectivamente, obteremos o produto de todos, exceto atodos e menos b. Fazendo isso para cada par e resultado recursivo com esse par faltando, obtemos o resultado final. O caso de comprimento ímpar é tratado especialmente por ter o elemento ímpar passado sem par para a etapa recursiva, e o produto dos elementos restantes retornados é o produto sem ele.

O número de multiplicações t(n)é n/2para o produto de emparelhamento, t(n/2)para a chamada recursiva e outro npara os produtos com elementos individuais. Isso dá t(n) = 1.5 * n + t(n/2)para estranho n. Usar uma contagem mais precisa para ímpar ne ignorar a multiplicação 1para o caso base fornece pontuação 2997para n=1000.

xnor
fonte
Isso é muito legal.
Arthur
Penso que a razão pela qual a pontuação é 2995 e não 2994, como na minha resposta é que ela calcula o produto de todos os números também na não potência de dois casos, que é posteriormente truncada. Talvez uma manipulação cuidadosa products_but_one'possa evitar isso retornando algo do tamanho correto.
nore 18/06
@nore eu achei que tinha uma multiplicação extra na minha contagem, porque eu esqueci o caso base tem um 1que é livre para multiplicar. Acho que o preenchimento 1 não afetou as coisas, mas limpei meu algoritmo para não usá-las.
xnor
Este código assume que a entrada é um número inteiro?
@Lembik Sim, mas apenas nas anotações de tipo opcionais. Vou mudar todos eles para float.
xnor
9

Haskell , pontuação 9974

partition :: [Float] -> ([Float], [Float])
partition = foldr (\a (l1,l2) -> (l2, a:l1)) ([],[])

(%) :: Float -> Float -> Float
a % 1 = a
1 % b = b
a % b = a*b

merge :: (Float, [Float]) -> (Float, [Float]) -> (Float, [Float])
merge (p1,r1) (p2, r2) = (p1%p2, map(%p1)r2 ++ map(%p2)r1)

missing_products' :: [Float] -> (Float, [Float])
missing_products' [a] = (a,[1])
missing_products' l = merge res1 res2
    where
        (l1, l2) = partition l
        res1 = missing_products' l1
        res2 = missing_products' l2

missing_products :: [Float] -> [Float]
missing_products = snd . missing_products'

Experimente online!

Uma estratégia de dividir e conquistar, muito reminiscente do tipo de mesclagem. Não faz nenhuma indexação.

A função partitiondivide a lista em metades o mais igual possível, colocando elementos alternados em lados opostos da partição. Mesclamos recursivamente os resultados (p,r)para cada uma das metades, com ra lista de produtos com uma falta e po produto geral.

Para a saída da lista completa, o elemento ausente deve estar em uma das metades. O produto com esse elemento ausente é um produto que falta na metade, multiplicado pelo produto completo para a outra metade. Portanto, multiplicamos cada produto por um faltando pelo produto completo da outra metade e fazemos uma lista dos resultados, como map(*p1)r2 ++ map(*p2)r1). Isso leva nmultiplicações, onde nestá o comprimento. Também precisamos criar um novo produto completo p1*p2para uso futuro, custando mais 1 multiplicação.

Isto dá a recursão geral para o número de operações t(n)com nmesmo: t(n) = n + 1 + 2 * t(n/2). O ímpar é semelhante, mas um dos sublistas é 1maior. Fazendo a recursão, obtemos n*(log_2(n) + 1)multiplicações, embora a distinção ímpar / par afete esse valor exato. Os valores até t(3)são aprimorados, não multiplicando 1, definindo uma variante (%)desses (*)atalhos the _*1ou 1*_cases.

Isso dá 9975multiplicações para n=1000. Acredito que a preguiça de Haskell significa que o produto geral não utilizado na camada externa não é calculado 9974; se estiver enganado, posso omitir explicitamente.

xnor
fonte
Você me venceu no carimbo de data e hora um minuto antes.
nore 18/06
Se for difícil descobrir exatamente a fórmula, fique à vontade para executá-la n = 1000e contar as operações aritméticas no código.
Arthur
Como nosso código é basicamente o mesmo, não entendo como você conseguiu 9974e não as 9975multiplicações n = 1000(no caso de calcular o produto geral na camada externa). Você incluiu um 1na entrada usada para testá-lo?
nore
@nore Você está certo, eu estava fora por um. Escrevi código para fazer a recursão pelo número de chamadas de função de multiplicação. Contar chamadas diretamente seria mais confiável - alguém sabe como eu faria isso em Haskell?
Xnor
1
@xnor você pode usar tracea partir Debug.Tracecom um catch-all | trace "call!" False = undefinedguarda, eu acho. Mas isso é feito unsafePerformIOsob o capô, então não é realmente uma grande melhoria.
Soham Chowdhury
6

Haskell , pontuação 2994

group :: [a] -> Either [(a, a)] (a, [(a, a)])
group [] = Left []
group (a : l) = case group l of
  Left pairs -> Right (a, pairs)
  Right (b, pairs) -> Left ((a, b) : pairs)

products_but_one :: Num a => [a] -> [a]
products_but_one [_] = [1]
products_but_one [a, b] = [b, a]
products_but_one l = case group l of
  Left pairs ->
    let subresults =
          products_but_one [a * b | (a, b) <- pairs]
    in do ((a, b), c) <- zip pairs subresults; [c * b, c * a]
  Right (extra, pairs) ->
    let subresult : subresults =
          products_but_one (extra : [a * b | (a, b) <- pairs])
    in subresult : do ((a, b), c) <- zip pairs subresults; [c * b, c * a]

Experimente online!

Como funciona

Esta é uma versão limpa do algoritmo do xnor que lida com o caso ímpar de maneira mais direta (editar: parece que o xnor o limpou da mesma maneira):

[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] por recursão ↦
[(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 recursão ↦
[(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, (ABCDEFG].

Anders Kaseorg
fonte
"Dada n números em uma matriz (você não pode assumir que eles são inteiros)," Nós não podemos assumir que eles são inteiros
5

O (n log n) operações, pontuação = 9974

Funciona com uma árvore binária.

Python

l = list(map(int, input().split()))
n = len(l)

p = [0] * n + l
for i in range(n - 1, 1, -1):
  p[i] = p[i + i] * p[i + i+1]

def mul(x, y):
  if y == None:
    return x
  return x * y

r = [None] * n + [[None]] * n
for i in range(n - 1, 0, -1):
  r[i] = [mul(p[i + i + 1], x) for x in r[i + i]] + [mul(p[i + i], x) for x in r[i + i + 1]]

u = r[1]
j = 1
while j <= n:
  j += j
print(u[n+n-j:] + u[:n+n-j])

Isso também requer operações de adição de lista e alguma aritmética em números que não são os valores de entrada; não tenho certeza se isso conta. A mulfunção existe para salvar n operações para o caso base, para evitar desperdiçá-las multiplicando por 1. Em qualquer caso, são operações O (n log n). A fórmula é exacto, se somente uma contagem de operações aritméticas sobre os números de entrada, com j = floor(log_2(n)): j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2.

Agradecemos ao @xnor por salvar uma operação com a idéia de não computar o produto externo!

A última parte é produzir os produtos na ordem do termo que falta.

nore
fonte
Se for difícil descobrir exatamente a fórmula, fique à vontade para executá-la n = 1000e contar as operações aritméticas no código.
Arthur
Eu contei 10975 operações ...?
HyperNeutrino
p[i] = p[i + i] * p[i + i+1]não é contado
HyperNeutrino
Isto é sobre n log2 n + noperações (que é O (nlogn) btw
HyperNeutrino 18/17/17
@HyperNeutrino as operações p[i] = p[i + i] * p[i + i + 1]devem ser salvas pela otimização da multiplicação. Eu poderia ter contado um número demais, no entanto.
nore 18/06
3

O ((n-2) * n) = O (n 2 ): Solução trivial

Esta é apenas a solução trivial que multiplica juntos cada um dos subconjuntos:

Python

def product(array): # Requires len(array) - 1 multiplication operations
    if not array: return 1
    result = array[0]
    for value in array[1:]:
        result *= value
    return result

def getSubsetProducts(array):
    products = []
    for index in range(len(array)): # calls product len(array) times, each time calling on an array of size len(array) - 1, which means len(array) - 2 multiplication operations called len(array) times
        products.append(product(array[:index] + array[index + 1:]))
    return products

Observe que isso também requer noperações de adição de lista; não tenho certeza se isso conta. Se isso não for permitido, product(array[:index] + array[index + 1:])poderá ser substituído por product(array[:index]) * product(array[index + 1:]), que altera a fórmula para O((n-1)*n).

HyperNeutrino
fonte
Você pode adicionar sua própria pontuação à resposta. 998 * 1000 neste caso.
Arthur
Não precisa de suas operações productfuncionais O(n)? um para cada elemento na matriz (althought isso pode facilmente ser alterados para O(n-1))
Roman Gräf
@ RomanGräf True. Vou mudar para O (n-1), mas obrigado por apontar isso.
HyperNeutrino
Isso foi alterado para atomic-code-golf ...
Erik o Outgolfer
@EriktheOutgolfer O que isso faz com que minha pontuação agora? A menos que eu esteja sendo flagrantemente estúpido, a etiqueta e as especificações não se contradizem agora?
HyperNeutrino
3

Python, 7540

Uma estratégia de mesclagem tripartida. Eu acho que posso fazer ainda melhor do que isso, com uma fusão ainda grande. É O (n log n).

EDIT: Corrigido um miscount.

count = 0
def prod(a, b):
    if a == 1: return b
    if b == 1: return a
    global count
    count += 1
    return a * b

def tri_merge(subs1, subs2, subs3):
    total1, missing1 = subs1
    total2, missing2 = subs2
    total3, missing3 = subs3

    prod12 = prod(total1, total2)
    prod13 = prod(total1, total3)
    prod23 = prod(total2, total3)

    new_missing1 = [prod(m1, prod23) for m1 in missing1]
    new_missing2 = [prod(m2, prod13) for m2 in missing2]
    new_missing3 = [prod(m3, prod12) for m3 in missing3]

    return prod(prod12, total3), new_missing1 + new_missing2 + new_missing3

def tri_partition(nums):
    split_size = len(nums) // 3
    a = nums[:split_size]
    second_split_length = split_size + (len(nums) % 3 == 2)
    b = nums[split_size:split_size + second_split_length]
    c = nums[split_size + second_split_length:]
    return a, b, c

def missing_products(nums):
    if len(nums) == 1: return nums[0], [1]
    if len(nums) == 0: return 1, []
    subs = [missing_products(part) for part in tri_partition(nums)]
    return tri_merge(*subs)

def verify(nums, res):
    actual_product = 1
    for num in nums:
        actual_product *= num
    actual_missing = [actual_product // num for num in nums]
    return actual_missing == res[1] and actual_product == res[0]

nums = range(2, int(input()) + 2)
res = missing_products(nums)

print("Verified?", verify(nums, res))
if max(res[1]) <= 10**10: print(res[1])

print(len(nums), count)

A função relevante é missing_products, que fornece ao produto geral e a todos os que faltam um elemento.

isaacg
fonte
você contou as multiplicações tri_merge? Além disso, você pode substituir o 2 * split_size + ...em tri_partitioncom split_size + split_size + ....
Roman Gräf
@ RomanGräf Eu reestruturei de acordo com sua sugestão.
Isaacg
1

dc, pontuação 2994

#!/usr/bin/dc -f

# How it works:
# The required products are
#
#   (b × c × d × e × ... × x × y × z)
# (a) × (c × d × e × ... × x × y × z)
# (a × b) × (d × e × ... × x × y × z)
# ...
# (a × b × c × d × e × ... × x) × (z)
# (a × b × c × d × e × ... × x × y)
#
# We calculate each parenthesised term by
# multiplying the one above (on the left) or below
# (on the right), for 2(n-2) calculations, followed
# by the n-2 non-parenthesised multiplications
# giving a total of 3(n-2) operations.

# Read input from stdin
?

# We will store input values into stack 'a' and
# accumulated product into stack 'b'.  Initialise
# stack b with the last value read.
sb

# Turnaround function at limit of recursion: print
# accumulated 'b' value (containing b..z above).
[Lbn[ ]nq]sG

# Recursive function - on the way in, we stack up
# 'a' values and multiply up the 'b' values.  On
# the way out, we multiply up the 'a' values and
# multiply each by the corresponding 'b' value.
[dSalb*Sb
z1=G
lFx
dLb*n[ ]n
La*]dsFx

# Do the last a*b multiplication
dLb*n[ ]n

# And we have one final 'a' value that doesn't have a
# corresponding 'b':
La*n

Estou assumindo que a comparação inteira z1=(que termina a recursão quando atingimos o último valor) é gratuita. Isso é equivalente aos gostos de foreachoutros idiomas.

Manifestações

for i in '2 3 5' '2 3 5 7' '0 2 3 5' '0 0 1 2 3 4'
do printf '%s => ' "$i"; ./127147.dc <<<"$i"; echo
done
2 3 5 => 15 10 6
2 3 5 7 => 105 70 42 30
0 2 3 5 => 30 0 0 0
0 0 1 2 3 4 => 0 0 0 0 0 0

Uma demonstração com entradas grandes e pequenas:

./127147.dc <<<'.0000000000000000000542101086242752217003726400434970855712890625 1 18446744073709551616'
18446744073709551616 1.0000000000000000000000000000000000000000000000000000000000000000 .0000000000000000000542101086242752217003726400434970855712890625
Toby Speight
fonte
1

C ++, pontuação: 5990, O ([2NlogN] / 3)

Esta implementação usa uma tabela de pesquisa de árvore binária. Minha primeira implementação foi O (NlogN), mas uma otimização de última hora, que procura o produto de todos os elementos da matriz menos um par, + 2 multiplicações salvas no dia. Acho que isso ainda pode ser otimizado um pouco mais, talvez outros 16% ...

Deixei alguns traços de depuração, apenas porque é mais fácil excluí-los do que reescrevê-los :)

[Editar] a complexidade real é medida em O ([2NlogN] / 3) para 100. Na verdade, é um pouco pior que O (NlogN) para pequenos conjuntos, mas tende para O ([NlogN] / 2) à medida que a matriz cresce. O muito grande (0.57.NlogN) para um conjunto de 1 milhão de elementos.

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <random>
#include <cstdlib>

using DataType = long double;

using DataVector = std::vector<DataType>;

struct ProductTree
{
    std::vector<DataVector> tree_;
    size_t ops_{ 0 };

    ProductTree(const DataVector& v) : ProductTree(v.begin(), v.end()) {}
    ProductTree(DataVector::const_iterator first, DataVector::const_iterator last)
    {
        Build(first, last);
    }

    void Build(DataVector::const_iterator first, DataVector::const_iterator last)
    {
        tree_.emplace_back(DataVector(first, last));

        auto size = std::distance(first, last);
        for (auto n = size; n >= 2; n >>= 1)
        {
            first = tree_.back().begin();
            last = tree_.back().end();

            DataVector v;
            v.reserve(n);
            while (first != last) // steps in pairs
            {
                auto x = *(first++);
                if (first != last)
                {
                    ++ops_;
                    x *= *(first++); // could optimize this out,small gain
                }
                v.push_back(x);
            }
            tree_.emplace_back(v);
        }
    }

    // O(NlogN) implementation... 
    DataVector Prod()
    {
        DataVector result(tree_[0].size());
        for (size_t i = 0; i < tree_[0].size(); ++i)
        {
            auto depth = tree_.size() - 1;
            auto k = i >> depth;
            result[i] = ProductAtDepth(i, depth);
        }
        return result;
    }

    DataType ProductAtDepth(size_t index, size_t depth) 
    {
        if (depth == 0)
        {
            return ((index ^ 1) < tree_[depth].size())
                ? tree_[depth][index ^ 1]
                : 1;
        }
        auto k = (index >> depth) ^ 1;

        if ((k < tree_[depth].size()))
        {
            ++ops_;
            return tree_[depth][k] * ProductAtDepth(index, depth - 1);
        }
        return ProductAtDepth(index, depth - 1);
    }    

    // O([3NlogN]/2) implementation... 
    DataVector Prod2()
    {
        DataVector result(tree_[0].size());
        for (size_t i = 0; i < tree_[0].size(); ++i)    // steps in pairs
        {
            auto depth = tree_.size() - 1;
            auto k = i >> depth;
            auto x = ProductAtDepth2(i, depth);
            if (i + 1 < tree_[0].size())
            {
                ops_ += 2;
                result[i + 1] = tree_[0][i] * x;
                result[i] = tree_[0][i + 1] * x;
                ++i;
            }
            else
            {
                result[i] = x;
            }
        }
        return result;
    }

    DataType ProductAtDepth2(size_t index, size_t depth)
    {
        if (depth == 1)
        {
            index = (index >> 1) ^ 1;
            return (index < tree_[depth].size())
                ? tree_[depth][index]
                : 1;
        }
        auto k = (index >> depth) ^ 1;

        if ((k < tree_[depth].size()))
        {
            ++ops_;
            return tree_[depth][k] * ProductAtDepth2(index, depth - 1);
        }
        return ProductAtDepth2(index, depth - 1);
    }

};


int main()
{
    //srand(time());

    DataVector data;
    for (int i = 0; i < 1000; ++i)
    {
        auto x = rand() & 0x3;          // avoiding overflow and zero vaolues for testing
        data.push_back((x) ? x : 1);
    }

    //for (int i = 0; i < 6; ++i)
    //{
    //  data.push_back(i + 1);
    //}

    //std::cout << "data:[";
    //for (auto val : data)
    //{
    //  std::cout << val << ",";
    //}
    //std::cout << "]\n";

    ProductTree pt(data);
    DataVector result = pt.Prod2();

    //std::cout << "result:[";
    //for (auto val : result)
    //{
    //  std::cout << val << ",";
    //}
    //std::cout << "]\n";
    std::cout << "N = " << data.size() << " Operations :" << pt.ops_ << '\n';

    pt.ops_ = 0;
    result = pt.Prod();

    //std::cout << "result:[";
    //for (auto val : result)
    //{
    //  std::cout << val << ",";
    //}
    //std::cout << "]\n";

    std::cout << "N = " << data.size() << " Operations :" << pt.ops_ << '\n';

    return 0;
}

Estou adicionando o algoritmo de @ nore, para ser completo. É muito bom e é o mais rápido.

class ProductFlat
{
private:
    size_t ops_{ 0 };

    void InitTables(const DataVector& v, DataVector& left, DataVector& right)
    {
        if (v.size() < 2)
        {
            return;
        }

        left.resize(v.size() - 1);
        right.resize(v.size() - 1);

        auto l = left.begin();
        auto r = right.rbegin();
        auto ol = v.begin();
        auto or = v.rbegin();

        *l = *ol++;
        *r = *or++;
        if (ol == v.end())
        {
            return;
        }

        while (ol + 1 != v.end())
        {
            ops_ += 2;
            *l = *l++ * *ol++;
            *r = *r++ * *or++;
        }
    }

public:
    DataVector Prod(const DataVector& v)
    {
        if (v.size() < 2)
        {
            return v;
        }

        DataVector result, left, right;
        InitTables(v, left, right);

        auto l = left.begin();
        auto r = right.begin();
        result.push_back(*r++);
        while (r != right.end())
        {
            ++ops_;
            result.push_back(*l++ * *r++);
        }
        result.push_back(*l++);
        return result;
    }

    auto Ops() const
    {
        return ops_;
    }
};
Michaël Roy
fonte