Algoritma tercepat untuk mengambil produk dari semua himpunan bagian

23

nAngka yang diberikan dalam array (Anda tidak dapat menganggap itu bilangan bulat), saya ingin menghitung produk dari semua subset ukuran n-1.

Anda dapat melakukan ini dengan mengalikan semua angka bersama dan kemudian membaginya dengan masing-masing secara berurutan, selama tidak ada angka yang nol. Namun, seberapa cepat Anda dapat melakukan ini tanpa divisi?

Jika Anda tidak mengizinkan pembagian, berapakah jumlah minimum operasi aritmatika (mis. Penggandaan dan penambahan) yang diperlukan untuk menghitung produk dari semua himpunan bagian ukuran n-1?

Jelas Anda bisa melakukannya dalam (n-1)*nmultiplikasi.

Untuk memperjelas, output adalah nproduk yang berbeda dan satu-satunya operasi selain dari membaca dan menulis ke memori diperbolehkan adalah perkalian, penambahan dan pengurangan.

Contoh

Jika input memiliki tiga angka 2,3,5, maka outputnya adalah tiga angka 15 = 3*5, 10 = 2*5dan 6 = 2*3.

Kriteria menang

Jawaban harus memberikan formula yang tepat untuk jumlah operasi aritmatika yang akan digunakan oleh kode mereka n. Untuk membuat hidup lebih sederhana, saya hanya akan memasukkan n = 1000formula Anda untuk menilai nilainya. Semakin rendah semakin baik.

Jika terlalu sulit untuk menghasilkan formula yang tepat untuk kode Anda, Anda bisa menjalankannya n = 1000dan menghitung operasi aritmatika dalam kode. Namun formula yang tepat adalah yang terbaik.

Anda harus menambahkan skor n=1000Anda ke jawaban Anda untuk perbandingan yang mudah.

Arthur
sumber
4
Bolehkah kita menghitung mengalikan dengan 1 sebagai gratis? Kalau tidak, saya akan mendefinisikan fungsi perkalian kustom yang melakukan ini.
xnor
3
Apakah akan melanggar aturan untuk melakukan sejumlah perkalian secara paralel dengan menggabungkan angka bersama dengan cukup banyak spacer 0 digit?
xnor
1
Apakah operasi seperti +pada indeks dihitung? Jika ini masalahnya, apakah array indexing juga dihitung? (karena itu adalah gula sintaksis untuk penambahan dan dereferencing).
Nore
2
@ lebih baik saya menyerah :) Hanya menghitung operasi aritmatika yang melibatkan input dalam beberapa cara.
Arthur
1
Jelas Anda bisa melakukannya dalam (n-1)*nmultiplikasi Maksud Anda (n-2)*n, bukan?
Luis Mendo

Jawaban:

25

Operasi Python, 3 (n-2), skor = 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)

Array leftdan masing-masing rightberisi produk terakumulasi dari array dari kiri dan dari kanan.

EDIT: Bukti bahwa 3 (n-2) adalah jumlah operasi optimal yang diperlukan untuk n> = 2, jika kita hanya menggunakan perkalian.

Kami akan melakukannya dengan induksi; oleh algoritma di atas, kita hanya perlu membuktikan bahwa untuk n> = 2, 3 (n-2) adalah batas bawah pada jumlah perkalian yang dibutuhkan.

Untuk n = 2, kita membutuhkan paling tidak 0 = 3 (2-2) perkalian, sehingga hasilnya sepele.

Misalkan n> 2, dan anggap untuk elemen n - 1, kita membutuhkan setidaknya 3 (n-3) perkalian. Pertimbangkan solusi untuk n elemen dengan perkalian k. Sekarang, kami menghapus elemen terakhir seolah-olah 1, dan menyederhanakan semua perkalian secara langsung olehnya. Kami juga menghapus perkalian yang mengarah ke produk dari semua elemen lain, karena yang satu tidak diperlukan karena tidak pernah dapat digunakan sebagai nilai perantara untuk mendapatkan produk n-2 dari elemen lain, karena pembagian tidak diperbolehkan. Ini meninggalkan kita dengan l perkalian, dan solusi untuk elemen n - 1.

Dengan hipotesis induksi, kita memiliki l> = 3 (n-3).

Sekarang, mari kita lihat berapa banyak perkalian yang dihapus. Salah satunya adalah yang mengarah ke produk dari semua elemen kecuali yang terakhir. Selain itu, elemen terakhir digunakan langsung dalam setidaknya dua perkalian: jika digunakan hanya dalam satu, maka itu digunakan ketika mengalikan dengan hasil antara yang terdiri dari beberapa produk dari elemen lain; katakanlah, tanpa kehilangan sifat umum, hasil antara ini termasuk elemen pertama dalam produk. Kemudian, tidak ada cara untuk mendapatkan produk dari semua elemen kecuali yang pertama, karena setiap produk yang mengandung elemen terakhir adalah elemen terakhir, atau mengandung elemen pertama.

Dengan demikian, kita memiliki k> = l + 3> = 3 (n-2), membuktikan teorema yang diklaim.

Nore
sumber
8
Hal ini ternyata sangat bersih di Haskell : f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l).
xnor
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Dennis
12

Haskell , skor 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

Cobalah online!

Katakanlah kita diberi daftar [a,b,c,d,e,f,g,h]. Kami pertama kali mengelompokkannya menjadi beberapa [[a,b],[c,d],[e,f],[g,h]]. Kemudian, kami mengulang daftar setengah ukuran pairsproduk mereka untuk mendapatkansubresults

[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)]

Jika kita mengambil elemen pertama (c*d)*(e*f)*(g*h), dan kalikan dengan bdan amasing-masing, kita mendapatkan produk dari semua tapi adan semua tapi b. Melakukan ini untuk setiap pasangan dan hasil rekursif dengan pasangan itu hilang, kami mendapatkan hasil akhir. Kasing panjang ganjil secara khusus ditangani dengan membiarkan elemen aneh dilewatkan tidak berpasangan ke langkah rekursif, dan produk dari elemen yang tersisa dikembalikan adalah produk tanpa itu.

Jumlah perkalian t(n)adalah n/2untuk produk pasangan, t(n/2)untuk panggilan rekursif, dan lainnya nuntuk produk dengan elemen individual. Ini memberi t(n) = 1.5 * n + t(n/2)aneh n. Menggunakan hitungan yang lebih tepat untuk nmengalikan ganjil dan mengabaikan dengan 1untuk kasus dasar memberikan skor 2997untuk n=1000.

Tidak
sumber
Ini sangat bagus.
Arthur
Saya pikir alasan mengapa skornya adalah 2995 dan bukan 2994 karena dalam jawaban saya adalah bahwa ia menghitung produk dari semua angka juga dalam non-kekuatan dua case, yang kemudian dipotong. Mungkin penanganan yang hati-hati products_but_one'dapat menghindarinya dengan mengembalikan sesuatu dengan panjang yang benar.
Nore
@Nore saya menemukan saya memiliki perkalian tambahan dalam hitungan saya karena saya lupa memiliki kasus dasar 1yang bebas untuk berkembang biak. Saya pikir padding 1 tidak mempengaruhi hal-hal, tetapi saya membersihkan algoritma saya untuk tidak menggunakannya.
xnor
Apakah kode ini menganggap inputnya bilangan bulat?
@Lembik Tidak, tetapi hanya dalam anotasi jenis opsional. Saya akan mengubah semuanya float.
xnor
9

Haskell , skor 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'

Cobalah online!

Strategi memecah-dan-menaklukkan, sangat mirip dengan jenis gabungan. Tidak melakukan pengindeksan.

Fungsi partitionmembagi daftar menjadi dua bagian yang sama dengan mungkin dengan menempatkan elemen bergantian di sisi yang berlawanan dari partisi. Kami menggabungkan hasil secara rekursif (p,r)untuk setiap bagian, dengan rdaftar produk-dengan-satu-hilang, dan pproduk keseluruhan.

Untuk output untuk daftar lengkap, elemen yang hilang harus berada di salah satu bagian. Produk dengan elemen yang hilang itu adalah produk satu-hilang untuk separuhnya, dikalikan dengan produk lengkap untuk setengah lainnya. Jadi, kami mengalikan setiap produk dengan satu yang hilang dengan produk lengkap dari setengah lainnya dan membuat daftar hasilnya map(*p1)r2 ++ map(*p2)r1). Ini membutuhkan nmultiplikasi, di mana npanjangnya. Kita juga perlu membuat produk lengkap baru p1*p2untuk penggunaan di masa mendatang, dengan biaya 1 kali lipat lebih banyak.

Hal ini memberikan rekursi umum untuk untuk jumlah operasi t(n)dengan nbahkan: t(n) = n + 1 + 2 * t(n/2). Yang aneh mirip, tetapi salah satu sublists 1lebih besar. Melakukan rekursi, kita mendapatkan n*(log_2(n) + 1)multiplikasi, meskipun perbedaan ganjil / genap mempengaruhi nilai pastinya. Nilai-nilai hingga t(3)ditingkatkan dengan tidak mengalikan dengan 1dengan mendefinisikan varian (%)dari (*)yang shortcut _*1atau 1*_kasus.

Ini memberikan 9975multiplikasi untuk n=1000. Saya percaya kemalasan Haskell berarti produk keseluruhan yang tidak digunakan di lapisan luar tidak dihitung 9974; jika saya salah, saya bisa menghilangkannya secara eksplisit.

Tidak
sumber
Anda mengalahkan saya dengan cap waktu satu menit sebelumnya.
Nore
Jika sulit untuk mengerjakan rumusnya dengan tepat, silakan jalankan saja n = 1000dan hitung operasi aritmatika dalam kode.
Arthur
Karena kode kami pada dasarnya sama, saya tidak mengerti bagaimana Anda melakukannya 9974dan tidak 9975memperbanyak untuk n = 1000(dalam hal menghitung keseluruhan produk di lapisan luar). Apakah Anda memasukkan 1dalam input yang Anda gunakan untuk mengujinya?
Nore
@nore Kau benar, aku pergi satu per satu. Saya menulis kode untuk melakukan rekursi untuk sejumlah panggilan fungsi perkalian. Menghitung panggilan secara langsung akan lebih dapat diandalkan - apakah ada yang tahu bagaimana saya akan melakukannya di Haskell?
xnor
1
@ xnor Anda dapat menggunakan tracedari Debug.Tracedengan | trace "call!" False = undefinedpenjaga menangkap semua , saya pikir. Tapi ini menggunakan di unsafePerformIObawah tenda, jadi itu tidak benar-benar perbaikan.
Soham Chowdhury
6

Haskell , skor 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]

Cobalah online!

Bagaimana itu bekerja

Ini adalah versi yang dibersihkan dari algoritma xnor yang menangani kasus aneh dengan cara yang lebih mudah (edit: sepertinya xnor telah membersihkannya dengan cara yang sama):

[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] dengan rekursi ↦
[(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)] dengan rekursi ↦
[(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].

Anders Kaseorg
sumber
"Diberikan n angka dalam array (Anda tidak dapat menganggap itu bilangan bulat)," Kami tidak dapat menganggap itu bilangan bulat
5

O (n log n) operasi, skor = 9974

Bekerja dengan pohon biner.

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])

Ini juga memerlukan operasi penambahan daftar, dan beberapa aritmatika pada angka yang bukan nilai input; tidak yakin apakah itu diperhitungkan. The mulfungsi di sana untuk menyelamatkan n operasi untuk kasus dasar, untuk menghindari pemborosan mereka dengan mengalikan dengan 1. Dalam hal apapun, ini adalah (n log n) O operasi. Formula yang tepat, jika hanya menghitung operasi aritmatika pada nomor masukan, dengan j = floor(log_2(n)): j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2.

Terima kasih kepada @xnor karena telah menghemat satu operasi dengan ide tidak menghitung produk luar!

Bagian terakhir adalah mengeluarkan produk sesuai dengan jangka waktu yang hilang.

Nore
sumber
Jika sulit untuk mengerjakan rumusnya dengan tepat, silakan jalankan saja n = 1000dan hitung operasi aritmatika dalam kode.
Arthur
Saya menghitung 10975 operasi ...?
HyperNeutrino
p[i] = p[i + i] * p[i + i+1]tidak dihitung
HyperNeutrino
Ini tentang n log2 n + noperasi (yaitu O (nlogn) btw
HyperNeutrino
@HyperNeutrino yang operasinya p[i] = p[i + i] * p[i + i + 1]harus disimpan oleh optimisasi multiplikasi. Saya mungkin menghitung terlalu banyak.
Nore
3

O ((n-2) * n) = O (n 2 ): Solusi Sepele

Ini hanya solusi sepele yang mengalikan masing-masing himpunan bagian:

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

Perhatikan bahwa ini juga membutuhkan noperasi penambahan daftar; tidak yakin apakah itu diperhitungkan. Jika itu tidak diizinkan, maka product(array[:index] + array[index + 1:])dapat diganti menjadi product(array[:index]) * product(array[index + 1:]), yang mengubah rumus menjadi O((n-1)*n).

HyperNeutrino
sumber
Anda dapat menambahkan skor Anda sendiri ke jawabannya. 998 * 1000 dalam hal ini.
Arthur
Tidak perlu operasi productfungsi Anda O(n)? satu untuk setiap elemen dalam array (walaupun ini dapat dengan mudah diubah menjadi O(n-1))
Roman Gräf
@ RomanGräf Benar. Saya akan mengubahnya ke O (n-1) tetapi terima kasih telah menunjukkannya.
HyperNeutrino
Ini telah diubah menjadi atom-kode-golf ...
Erik the Outgolfer
@EriktheOutgolfer Apa yang membuat skor saya sekarang? Kecuali jika saya benar-benar bodoh, bukankah tag dan spesifikasinya saling bertentangan sekarang?
HyperNeutrino
3

Python, 7540

Strategi penggabungan tripartit. Saya pikir saya bisa melakukan lebih baik dari ini, dengan penggabungan yang belum besar. Ini O (n log n).

EDIT: Memperbaiki salah perhitungan.

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)

Fungsi yang relevan adalah missing_products, yang memberikan keseluruhan produk dan semua yang memiliki elemen yang hilang.

isaacg
sumber
apakah Anda menghitung perkalian tri_merge? Anda juga dapat mengganti 2 * split_size + ...di tri_partitiondengan split_size + split_size + ....
Roman Gräf
@ RomanGräf Saya merestrukturisasi sesuai saran Anda.
isaacg
1

dc, skor 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

Saya mengasumsikan bahwa perbandingan integer z1=(yang mengakhiri rekursi ketika kita mencapai nilai terakhir) adalah gratis. Ini setara dengan suka foreachdalam bahasa lain.

Demonstrasi

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

Demo dengan input besar dan kecil:

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

C ++, skor: 5990, O ([2NlogN] / 3)

Implementasi ini menggunakan tabel pencarian pohon biner. Implementasi pertama saya adalah O (NlogN), tetapi optimasi menit terakhir, yang mencari produk dari semua elemen array minus sepasang, + 2 perkalian disimpan hari. Saya pikir ini masih bisa dioptimalkan sedikit lebih jauh, mungkin 16% lagi ...

Saya telah meninggalkan beberapa jejak debugging, hanya karena lebih mudah untuk menghapusnya daripada menulis ulang mereka :)

[Sunting] kompleksitas sebenarnya diukur pada O ([2NlogN] / 3) untuk 100. Ini sebenarnya sedikit lebih buruk daripada O (NlogN) untuk set kecil, tetapi cenderung ke arah O ([NlogN] / 2) ketika array tumbuh sangat besar O (0,57.NlogN) untuk satu set 1 juta elemen.

#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;
}

Saya menambahkan algoritma @ nore, untuk kelengkapan. Sangat bagus, dan tercepat.

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
sumber