取所有子集乘积的最快算法


23

给定n数组中的数字(不能假设它们是整数),我想计算size的所有子集的乘积n-1

您可以通过将所有数字相乘然后依次除以一个数字(只要这些数字都不为零)来实现。但是,您可以多快完成一次不分割的操作?

如果您不允许除法,那么计算大小为n-1的所有子集的乘积所需的最小算术运算数(例如乘法和加法)是多少?

显然,您可以(n-1)*n乘以乘法。

要澄清的是,输出是n不同的乘积,除了对内存的读写操作外,唯一的操作是乘法,加法和减法。

如果输入有三个数字2,3,5,则输出为三个数字15 = 3*510 = 2*56 = 2*3

获奖标准

答案应给出其代码将使用的算术运算次数的精确公式n。为了简化生活,我将插入n = 1000您的公式来判断其得分。越低越好。

如果很难为您的代码生成精确的公式,则可以对其运行n = 1000并计算代码中的算术运算。但是,最好使用一个精确的公式。

您应该将自己的分数添加n=1000到答案中以便于比较。


4
我们可以将乘以1算作免费吗?否则,我将定义一个执行此操作的自定义乘法功能。
xnor

3
通过将数字与足够多的间隔0数字连接在一起来并行进行一整堆乘法是否违反规则?
xnor

1
诸如+索引的操作是否计数?如果是这种情况,数组索引也算在内吗?(因为毕竟要添加和取消引用语法糖)。
诺雷

2
@nore好吧,我输入:)只是计算以某种方式涉及输入的算术运算。
亚瑟

1
显然,您可以(n-1)*n乘法来完成它,(n-2)*n吗?
Luis Mendo

Answers:


25

Python,3(n-2)次运算,得分= 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)

数组left和分别right包含数组的从左到右的累积乘积。

编辑:如果我们仅使用乘法,则证明3(n-2)是n> = 2所需的最佳运算数。

我们将通过归纳来做到这一点;通过上述算法,我们仅需证明对于n> = 2,3(n-2)是所需乘法次数的下限。

对于n = 2,我们至少需要0 = 3(2-2)乘法,因此结果微不足道。

令n> 2,并假设对于n-1个元素,我们至少需要3(n-3)个乘法。考虑一个具有k个乘法的n个元素的解。现在,我们删除这些元素的最后一个,就好像它是1,然后直接用它简化所有的乘法。我们还删除了导致所有其他元素乘积的乘积,因为不需要该乘积,因为由于不允许除法,它永远不能用作获取其他元素n-2乘积的中间值。这给了我们l个乘法,以及n-1个元素的解决方案。

通过归纳假设,我们有l> = 3(n-3)。

现在,让我们看一下删除了多少个乘法。其中之一是导致除最后一个元素之外的所有元素相乘的元素。此外,最后一个元素至少直接用于两次乘法:如果仅一次使用,则在与包含其他元素的乘积的中间结果相乘时使用。可以说,在不失去一般性的前提下,这种中间结果包括了产品中的第一个元素。然后,除了第一个元素外,没有其他方法可以得到所有元素的乘积,因为包含最后一个元素的每个产品要么是最后一个元素,要么包含第一个元素。

因此,我们有k> = l + 3> = 3(n-2),证明了所要求的定理。


8
这证明非常干净哈斯克尔f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l)
xnor

评论不作进一步讨论;此对话已转移至聊天
丹尼斯,

12

Haskell,得分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

在线尝试!

说我们得到了清单[a,b,c,d,e,f,g,h]。我们首先将其成对分组[[a,b],[c,d],[e,f],[g,h]]。然后,我们递归于pairs他们产品的一半尺寸列表以获取subresults

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

如果我们采取的第一个元素(c*d)*(e*f)*(g*h),并用它乘ba分别,我们得到了,但所有的产品a和所有,但b。对每个对执行此操作,并在缺少该对的情况下执行递归结果,我们得到最终结果。奇数长度的情况是通过将奇数元素不成对传递给递归步骤来专门处理的,返回的其余元素的乘积就是没有它的乘积。

乘法的数量t(n)n/2用于配对产品,t(n/2)为递归调用,另外n的产品时,单个元素。这让人t(n) = 1.5 * n + t(n/2)感到奇怪n。使用更精确的计数奇数n,并且忽略与相乘1为基础案例给出得分2997n=1000


很好
亚瑟

我认为得分为2995而不是2994的原因是我在答案中的原因是,它也计算所有数字的乘积,并且在两种情况的非幂中都将其截断。也许小心处理products_but_one'可以通过返回正确长度的东西来避免这种情况。
诺雷

@nore我发现我的计数中有一个额外的乘法,因为我忘记了基本情况下1可以自由乘法的乘积。我认为padding 1不会影响事物,但是我清理了算法以不使用它们。
xnor

此代码是否假定输入为整数?

@Lembik可以,但是仅在可选的类型注释中。我将它们全部更改为float
xnor

9

Haskell得分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'

在线尝试!

分而治之的策略,非常让人联想到合并排序。不做任何索引。

该函数partition通过将交替的元素放在分区的相对侧,将列表分成尽可能相等的两半。我们以递归方式合并(p,r)每个半部分的结果r,以及缺少一个的产品列表和p整个产品。

对于完整列表的输出,缺少的元素必须在一半中。缺少该元素的乘积是其所占一半的一个单项乘积,乘以另一半的全乘积。因此,我们将每一个缺失的乘积乘以另一半的乘积,得出结果列表,如map(*p1)r2 ++ map(*p2)r1)。这需要n乘,n长度在哪里。我们还需要制作一个新的完整产品p1*p2以供将来使用,需要增加1倍的乘法运算。

这给出了t(n)具有n偶数的操作数的一般递归t(n) = n + 1 + 2 * t(n/2)。奇数个相似,但是子列表之一1更大。进行递归,我们得到了n*(log_2(n) + 1)乘法,尽管奇/偶区别影响了该精确值。通过定义快捷方式the 或case 的变体而t(3)不是乘以来提高up的值。1(%)(*)_*11*_

给出的9975乘法n=1000。我相信Haskell的懒惰意味着不会为外层计算未使用的总乘积9974;如果我弄错了,我可以明确地省略它。


你在一分钟之前打败了我。
诺雷

如果很难精确地计算出公式,则可以随意运行它n = 1000并计算代码中的算术运算。
亚瑟

由于我们的代码基本相同,因此我不了解您的操作方式(9974而不是9975乘法)n = 1000(在外层计算整体乘积的情况)。您是否1在用于测试的输入中包含a ?
nore

@nore你是对的,我走了一个。我编写了代码来对乘法函数调用的次数进行递归。直接计算通话次数会更可靠-有人知道我会在Haskell中这样做吗?
xnor

1
@xnor可以使用traceDebug.Trace一个包罗万象的| trace "call!" False = undefined后卫,我想。但这是unsafePerformIO在后台使用的,因此并没有太大的改进。
Soham Chowdhury

6

Haskell,得分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]

在线尝试!

怎么运行的

这是xnor算法的清理版本,它以更直接的方式处理奇怪的情况(编辑:xnor似乎也以相同的方式清理了):

[a,b,c,d,e,f,g]↦
[a,bc,de,fg]↦
[(bc)(de)(fg),a(de)(fg),a(bc)( fg),a(bc)(de)]递归↦
[(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)]通过递归↦
[(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]。


“给定数组中的n个数字(不能假设它们是整数),”“我们不能假设它们是整数

5

O(n log n)次运算,得分= 9974

与二叉树一起使用。

蟒蛇

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

这还需要列表加法运算,以及对不是输入值的数字的一些算术运算。不知道这是否重要。该mul函数用于为基本情况保存n个操作,以避免因乘以1而浪费它们。在任何情况下,这都是O(n log n)操作。如果仅计算输入数字的算术运算,则精确的公式为j = floor(log_2(n))j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2

感谢@xnor节省了一个不计算外部产品的想法!

最后一部分是按缺失术语的顺序输出产品。


如果很难精确地计算出公式,则可以随意运行它n = 1000并计算代码中的算术运算。
亚瑟

我算了10975次手术...?
HyperNeutrino

p[i] = p[i + i] * p[i + i+1]不计算在内
-HyperNeutrino

这是关于n log2 n + n操作的(这是O(nlogn)btw
HyperNeutrino

@HyperNeutrino p[i] = p[i + i] * p[i + i + 1]应该通过乘法优化来保存其中的操作。但是,我可能算得太多了。
诺雷

3

O((n-2)* n)= O(n 2):平凡解

这只是将每个子集相乘的简单解决方案:

蟒蛇

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

请注意,这还需要n列表添加操作。不知道这是否重要。如果不允许这样做,则product(array[:index] + array[index + 1:])可以将其替换为product(array[:index]) * product(array[index + 1:]),从而将公式更改为O((n-1)*n)


您可以将自己的分数添加到答案中。在这种情况下为998 * 1000。
亚瑟

不需要您的product功能O(n)操作吗?数组中每个元素一个(尽管可以轻松更改为O(n-1)
RomanGräf17年

@RomanGräf是的。我将其更改为O(n-1),但感谢您指出。
HyperNeutrino

这已更改为atomic-code-golf ...
Egg the Outgolfer

@EriktheOutgolfer现在我的成绩是什么?除非我公然愚蠢,否则标签和规格现在不矛盾吗?
HyperNeutrino

3

巨蟒,7540

三方合并策略。我认为我可以通过更大的合并来做得更好。是O(n log n)。

编辑:修正了错误计数。

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)

相关的功能是missing_products,它给出了整体产品以及所有缺少元素的产品。


你在里面算乘法tri_merge吗?您也可以替换2 * split_size + ...tri_partitionsplit_size + split_size + ...
RomanGräf17年

@RomanGräf我根据您的建议进行了重组。
isaacg

1

DC,得分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

我假设整数比较z1=(在达到最后一个值时终止递归)是免费的。这等效foreach于其他语言中的。

示威

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

具有大量输入和少量输入的演示:

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

1

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

此实现使用二叉树查找表。我的第一个实现是O(NlogN),但是最后一分钟的优化是查找所有数组元素减去一对的乘积,结果节省了2乘。我认为这仍可以进一步优化,可能再优化16%...

我留下了一些调试痕迹,只是因为删除它们比重写它们容易:)

[编辑]实际复杂度的测量值为100的O([2NlogN] / 3)。对于小集合,它实际上比O(NlogN)差一点,但是随着数组的增长趋于O([NlogN] / 2)非常大的O(0.57.NlogN),用于一百万个元素的集合。

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

为了完整性,我添加了@nore的算法。这真的很好,并且是最快的。

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_;
    }
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.