Thuật toán nhanh nhất để lấy sản phẩm của tất cả các tập hợp con


23

Cho các nsố trong một mảng (bạn không thể cho rằng chúng là số nguyên), tôi muốn tính sản phẩm của tất cả các tập hợp con có kích thước n-1.

Bạn có thể làm điều này bằng cách nhân tất cả các số với nhau và sau đó chia cho từng số một, miễn là không có số nào bằng không. Tuy nhiên, làm thế nào nhanh chóng bạn có thể làm điều này mà không có phân chia?

Nếu bạn không cho phép chia, số lượng hoạt động số học tối thiểu (ví dụ: nhân và cộng) là bao nhiêu để tính sản phẩm của tất cả các tập con có kích thước n-1?

Rõ ràng bạn có thể làm điều đó trong (n-1)*nphép nhân.

Để làm rõ, đầu ra là ncác sản phẩm khác nhau và các hoạt động duy nhất ngoài việc đọc và ghi vào bộ nhớ được phép là nhân, cộng và trừ.

Thí dụ

Nếu đầu vào gồm ba chữ số 2,3,5, sau đó đầu ra là ba số 15 = 3*5, 10 = 2*56 = 2*3.

Tiêu chí chiến thắng

Các câu trả lời sẽ đưa ra một công thức chính xác cho số lượng các phép toán số học mà mã của chúng sẽ sử dụng theo thuật ngữ n. Để làm cho cuộc sống đơn giản, tôi sẽ chỉ cần cắm n = 1000vào công thức của bạn để đánh giá điểm số của nó. Càng thấp càng tốt.

Nếu quá khó để tạo ra một công thức chính xác cho mã của bạn, bạn có thể chạy nó n = 1000và đếm các phép toán số học trong mã. Một công thức chính xác sẽ là tốt nhất tuy nhiên.

Bạn nên thêm điểm số n=1000của bạn cho câu trả lời của bạn để so sánh dễ dàng.


4
Chúng ta có thể tính nhân với 1 là miễn phí không? Mặt khác, tôi định nghĩa một hàm nhân tùy chỉnh thực hiện điều này.
xnor

3
Nó sẽ trái với các quy tắc để thực hiện một loạt các phép nhân song song bằng cách ghép các số với nhau với đủ nhiều chữ số 0 spacer?
xnor

1
Làm các hoạt động như +trên chỉ số đếm? Nếu đây là trường hợp, chỉ số mảng cũng được tính? (vì đó là sau khi tất cả đường cú pháp cho bổ sung và hội nghị).
nore

2
@nore OK Tôi cho vào :) Chỉ cần đếm các phép toán số học liên quan đến đầu vào theo một cách nào đó.
Arthur

1
Rõ ràng bạn có thể làm điều đó trong (n-1)*nphép nhân Bạn có ý nghĩa (n-2)*n, phải không?
Luis Mendo

Câu trả lời:


25

Python, 3 (n-2) phép toán, điểm = 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)

Các mảng leftrightchứa các sản phẩm tích lũy của mảng từ bên trái và bên phải, tương ứng.

EDIT: Chứng minh rằng 3 (n-2) là số lượng hoạt động tối ưu cần thiết cho n> = 2, nếu chúng ta chỉ sử dụng phép nhân.

Chúng tôi sẽ làm điều đó bằng cảm ứng; bằng thuật toán trên, chúng ta chỉ cần chứng minh rằng với n> = 2, 3 (n-2) là giới hạn thấp hơn về số lượng nhân cần thiết.

Với n = 2, chúng ta cần ít nhất 0 = 3 (2-2) phép nhân, vì vậy kết quả là không đáng kể.

Đặt n> 2 và giả sử cho n - 1 phần tử, chúng ta cần ít nhất 3 (n-3) phép nhân. Xét một giải pháp cho n phần tử với k nhân. Bây giờ, chúng tôi loại bỏ phần tử cuối cùng như thể là 1 và đơn giản hóa tất cả các phép nhân trực tiếp bằng nó. Chúng tôi cũng loại bỏ phép nhân dẫn đến sản phẩm của tất cả các yếu tố khác, vì yếu tố đó là không cần thiết vì nó không bao giờ có thể được sử dụng làm giá trị trung gian để lấy sản phẩm của n-2 của các yếu tố khác, vì không được phép chia. Điều này để lại cho chúng tôi các phép nhân l và một giải pháp cho n - 1 phần tử.

Theo giả thuyết cảm ứng, ta có l> = 3 (n-3).

Bây giờ, chúng ta hãy xem có bao nhiêu phép nhân đã bị xóa. Một trong số đó là một trong những sản phẩm dẫn đến sản phẩm của tất cả các yếu tố ngoại trừ cuối cùng. Hơn nữa, phần tử cuối cùng được sử dụng trực tiếp trong ít nhất hai phép nhân: nếu nó chỉ được sử dụng trong một, thì nó được sử dụng khi nhân với một kết quả trung gian bao gồm một số sản phẩm của các yếu tố khác; giả sử, không mất tính tổng quát, kết quả trung gian này bao gồm yếu tố đầu tiên trong sản phẩm. Sau đó, không có cách nào để có được sản phẩm của tất cả các phần tử, nhưng phần đầu tiên, vì mọi sản phẩm có chứa phần tử cuối cùng đều là phần tử cuối cùng hoặc chứa phần tử đầu tiên.

Do đó, chúng ta có k> = l + 3> = 3 (n-2), chứng minh định lý được yêu cầu.


8
Điều này hóa ra rất sạch sẽ trong Haskell : f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l).
xnor

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Dennis

12

Haskell , điểm 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

Hãy thử trực tuyến!

Nói rằng chúng tôi đưa ra danh sách [a,b,c,d,e,f,g,h]. Đầu tiên chúng tôi nhóm nó thành cặp [[a,b],[c,d],[e,f],[g,h]]. Sau đó, chúng tôi lặp lại danh sách pairscác sản phẩm của họ để có được một nửasubresults

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

Nếu chúng ta lấy phần tử đầu tiên (c*d)*(e*f)*(g*h), và nhân nó với batương ứng, chúng ta sẽ có được sản phẩm của tất cả nhưng avà tất cả nhưng b. Làm điều này cho mọi cặp và kết quả đệ quy với cặp đó bị thiếu, chúng tôi nhận được kết quả cuối cùng. Trường hợp độ dài lẻ được xử lý đặc biệt bằng cách chuyển phần tử lẻ sang bước đệ quy và sản phẩm của các phần tử còn lại được trả về là sản phẩm không có phần tử đó.

Số lượng nhân t(n)n/2cho sản phẩm ghép nối, t(n/2)cho cuộc gọi đệ quy và một số khác ncho các sản phẩm có các yếu tố riêng lẻ. Điều này t(n) = 1.5 * n + t(n/2)cho lẻ n. Sử dụng một số lượng chính xác hơn cho lẻ nvà bỏ qua nhân với 1đối với trường hợp cơ sở cho điểm 2997cho n=1000.


Điều này là rất tốt đẹp.
Arthur

Tôi nghĩ lý do tại sao điểm số là 2995 chứ không phải 2994 như trong câu trả lời của tôi là vì nó tính tích của tất cả các số cũng như trong trường hợp không có lũy thừa của hai trường hợp, sau đó bị cắt cụt. Có lẽ xử lý cẩn thận products_but_one'có thể tránh điều đó bằng cách trả lại một cái gì đó có độ dài chính xác.
nore

@nore Tôi thấy tôi có một phép nhân thêm trong số đếm của tôi vì tôi quên trường hợp cơ sở có một số 1đó là miễn phí để nhân. Tôi nghĩ phần đệm 1 không ảnh hưởng đến mọi thứ, nhưng tôi đã dọn sạch thuật toán của mình để không sử dụng chúng.
xnor

Liệu mã này giả định đầu vào là số nguyên?

@Lembik Nó có, nhưng chỉ trong các chú thích loại tùy chọn. Tôi sẽ thay đổi tất cả thành float.
xnor

9

Haskell , điểm 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'

Hãy thử trực tuyến!

Một chiến lược phân chia và chinh phục, rất gợi nhớ về loại hợp nhất. Không làm bất kỳ chỉ mục.

Hàm partitionchia danh sách thành hai nửa bằng nhau bằng cách đặt các phần tử xen kẽ vào các cạnh đối diện của phân vùng. Chúng tôi kết hợp đệ quy các kết quả (p,r)cho từng nửa, với rdanh sách các sản phẩm thiếu một và ptổng thể sản phẩm.

Đối với đầu ra cho danh sách đầy đủ, phần tử bị thiếu phải nằm trong một trong các nửa. Sản phẩm bị thiếu yếu tố đó là một sản phẩm thiếu một nửa cho một nửa, nhân với toàn bộ sản phẩm cho nửa còn lại. Vì vậy, chúng tôi nhân từng sản phẩm với một sản phẩm bị thiếu với toàn bộ sản phẩm của nửa kia và lập danh sách kết quả, như map(*p1)r2 ++ map(*p2)r1). Điều này có nphép nhân, trong đó nlà chiều dài. Chúng tôi cũng cần tạo ra một sản phẩm đầy đủ mới p1*p2để sử dụng trong tương lai, tốn thêm 1 lần nhân.

Điều này đưa ra đệ quy chung cho số lượng hoạt động t(n)với n: t(n) = n + 1 + 2 * t(n/2). Cái lẻ tương tự, nhưng một trong những danh sách con 1lớn hơn. Thực hiện đệ quy, chúng ta có được n*(log_2(n) + 1)phép nhân, mặc dù sự phân biệt lẻ / chẵn ảnh hưởng đến giá trị chính xác đó. Các giá trị tối đa t(3)được cải thiện bằng cách không nhân lên bằng 1cách xác định một biến thể (%)của (*)phím tắt đó _*1hoặc 1*_các trường hợp.

Điều này cho 9975phép nhân cho n=1000. Tôi tin rằng sự lười biếng của Haskell có nghĩa là sản phẩm tổng thể chưa sử dụng ở lớp ngoài không được tính toán 9974; nếu tôi nhầm, tôi có thể bỏ qua nó một cách rõ ràng.


Bạn đánh bại tôi bằng dấu thời gian một phút trước đó.
nore

Nếu thật khó để tìm ra công thức chính xác, cứ thoải mái chạy nó n = 1000và đếm các phép toán số học trong mã.
Arthur

Vì mã của chúng tôi về cơ bản là giống nhau, tôi không hiểu cách bạn nhận được 9974và không 9975nhân cho n = 1000(trong trường hợp tính toán tổng thể sản phẩm ở lớp ngoài). Bạn đã bao gồm một 1trong đầu vào bạn sử dụng để kiểm tra nó?
nore

@nore Bạn nói đúng, tôi đã nghỉ một. Tôi đã viết mã để thực hiện đệ quy cho số lần gọi hàm nhân. Đếm các cuộc gọi trực tiếp sẽ đáng tin cậy hơn - có ai biết tôi sẽ làm điều đó như thế nào trong Haskell không?
xnor

1
@xnor bạn có thể sử dụng tracetừ Debug.Tracevới một nhận tất cả | trace "call!" False = undefinedcảnh giác, tôi nghĩ vậy. Nhưng điều này sử dụng unsafePerformIOdưới mui xe, vì vậy nó không thực sự là một cải tiến nhiều.
Soham Chowdhury

6

Haskell , điểm 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]

Hãy thử trực tuyến!

Làm thế nào nó hoạt động

Đây là phiên bản được làm sạch của thuật toán xnor , xử lý trường hợp kỳ lạ theo cách đơn giản hơn (chỉnh sửa: có vẻ như xnor đã làm sạch nó theo cùng một cách):

[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] bằng cách đệ quy ↦
[(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)] bằng cách đệ quy ↦
[(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].


"Cho n số trong một mảng (bạn không thể cho rằng chúng là số nguyên)", Chúng tôi không thể cho rằng chúng là số nguyên

5

Hoạt động O (n log n), điểm = 9974

Hoạt động với cây nhị phân.

Con trăn

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

Điều này cũng yêu cầu các hoạt động bổ sung danh sách và một số số học trên các số không phải là giá trị đầu vào; không chắc chắn nếu tính. Các mulchức năng là có để tiết kiệm n hoạt động đối với trường hợp cơ sở, để tránh lãng phí chúng bằng cách nhân 1. Trong mọi trường hợp, điều này là O (n log n) hoạt động. Công thức chính xác là, nếu chỉ tính các phép tính số học trên các số đầu vào, với j = floor(log_2(n)): j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2.

Cảm ơn @xnor vì đã lưu một thao tác với ý tưởng không tính toán sản phẩm bên ngoài!

Phần cuối cùng là xuất các sản phẩm theo thứ tự còn thiếu.


Nếu thật khó để tìm ra công thức chính xác, hãy thoải mái chạy nó n = 1000và đếm các phép toán số học trong mã.
Arthur

Tôi đếm 10975 hoạt động ...?
HyperNeutrino

p[i] = p[i + i] * p[i + i+1]không được tính
HyperNeutrino

Đây là về các n log2 n + nhoạt động (đó là O (nlogn) btw
HyperNeutrino

@HyperNeutrino các hoạt động trong p[i] = p[i + i] * p[i + i + 1]nên được lưu bằng cách tối ưu hóa nhân. Tôi có thể đã đếm quá nhiều, tuy nhiên.
nore

3

O ((n-2) * n) = O (n 2 ): Giải pháp tầm thường

Đây chỉ là giải pháp tầm thường nhân với mỗi tập hợp con:

Con trăn

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

Lưu ý rằng điều này cũng yêu cầu ncác hoạt động bổ sung danh sách; không chắc chắn nếu tính. Nếu điều đó không được phép, thì product(array[:index] + array[index + 1:])có thể thay thế thành product(array[:index]) * product(array[index + 1:]), thay đổi công thức thành O((n-1)*n).


Bạn có thể thêm điểm số của riêng bạn để trả lời. 998 * 1000 trong trường hợp này.
Arthur

Không cần hoạt động productchức năng của bạn O(n)? một cho mỗi phần tử trong mảng (mặc dù điều này có thể dễ dàng thay đổi thành O(n-1))
Roman Gräf

@ RomanGräf Đúng. Tôi sẽ đổi nó thành O (n-1) nhưng cảm ơn vì đã chỉ ra điều đó.
HyperNeutrino

Điều này đã được đổi thành golf-code-nguyên tử ...
Erik the Outgolfer

@EriktheOutgolfer Điều gì làm cho điểm của tôi bây giờ? Trừ khi tôi đang ngu ngốc một cách trắng trợn, không phải là thẻ và thông số kỹ thuật mâu thuẫn với nhau bây giờ?
HyperNeutrino

3

Con trăn, 7540

Một chiến lược hợp nhất ba bên. Tôi nghĩ rằng tôi có thể làm tốt hơn thế này, với một sự hợp nhất lớn. Đó là O (n log n).

EDIT: Đã sửa lỗi sai.

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)

Các chức năng có liên quan là missing_products, cung cấp cho tổng thể sản phẩm và tất cả những người có một yếu tố còn thiếu.


bạn đã tính các phép nhân trong tri_merge? Ngoài ra bạn có thể thay thế 2 * split_size + ...trong tri_partitionvới split_size + split_size + ....
Roman Gräf

@ RomanGräf Tôi tái cấu trúc nó theo đề nghị của bạn.
isaacg

1

đc, điểm 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

Tôi giả sử rằng so sánh số nguyên z1=(chấm dứt đệ quy khi chúng ta đạt đến giá trị cuối cùng) là miễn phí. Điều này tương đương với foreachcác ngôn ngữ khác.

Biểu tình

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

Một bản demo với đầu vào lớn và nhỏ:

./127147.dc <<<'.0000000000000000000542101086242752217003726400434970855712890625 1 18446744073709551616'
18446744073709551616 1.0000000000000000000000000000000000000000000000000000000000000000 .0000000000000000000542101086242752217003726400434970855712890625

1

C ++, điểm: 5990, O ([2NlogN] / 3)

Việc thực hiện này sử dụng một bảng tra cứu cây nhị phân. Triển khai đầu tiên của tôi là O (NlogN), nhưng tối ưu hóa vào phút cuối, tìm kiếm sản phẩm của tất cả các phần tử mảng trừ đi một cặp, + 2 phép nhân được lưu trong ngày. Tôi nghĩ rằng điều này vẫn có thể được tối ưu hóa hơn một chút, có thể là 16% khác ...

Tôi đã để lại một số dấu vết sửa lỗi, chỉ vì việc xóa chúng dễ dàng hơn là viết lại chúng :)

[Chỉnh sửa] độ phức tạp thực tế được đo tại O ([2NlogN] / 3) cho 100. Nó thực sự kém hơn một chút so với O (NlogN) đối với các tập nhỏ, nhưng có xu hướng về O ([NlogN] / 2) khi mảng tăng lên O rất lớn (0,57.NlogN) cho tập hợp 1 triệu phần tử.

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

Tôi đang thêm thuật toán của @ nore để hoàn thiện. Nó thực sự tốt đẹp, và là nhanh nhất.

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_;
    }
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.