最小化权重之和,使加权和为零

时间:2017-03-29 17:34:07

标签: algorithm dynamic-programming weighted-average

给定n <= 1000个整数x(1), x(2), ..., x(n)其中|x(i)| <= 1000。我们希望为每个元素指定非负整数权重c(1), c(2), ..., c(n),以便c(1) * x(1) + ... + c(n) * x(n) = 0。让S = c(1) + ... + c(n)。我们需要S > 0,我们希望最小化S

我们可以二进制搜索最小S,对于某些特定S,我们可以通过构建dp(totalWeight, position, sum)来进行动态编程,但这样做太慢了。如何更快地解决?

1 个答案:

答案 0 :(得分:3)

假设至少有一个正面和至少一个负面重量(否则问题没有解决方案)。我们知道S最多是2000,因为如果有权重-c和+ d,则d * -c + c * d = 0.并且由于c,d <= 1000,我们知道S(最小正解)是使用2000个权重,最大可能总数为200万,最小可能总数为负200万。

现在,我们计算可以总计0到2百万的最小正权重数。

N = 2000000
p = [0] + [infinity] * N
for w in positive weights:
    for i = w ... N:
        p[i] = min(p[i], p[i-w]+1)

我们对负重量做同样的事情:

n = [0] + [infinity] * N
for w in negative weights:
    for i = -w ... N:
        n[i] = min(n[i], n[i+w]+1)

为了找到解决方案,我们找到两个数组的最小总和:

S = infinity
for i = 1 ... N:
    S = min(S, n[i] + p[i])

为了加快速度,可以找到S的更好界限(这会减少我们需要考虑的N)。设-c为最接近0的负权重,d为最接近0的正权重,e为最大量的权重。然后S <= c + d,因此N可以减少到(c + d)e。事实上,人们可以做得更好:如果-c和d是任何两个负/正权重,那么d / gcd(c,d)* -c + c / gcd(c,d)* d = 0,所以对于-ca负重量和da正重量,S以min((d + c)/ gcd(c,d)为界。

将所有这些整合到一个Go解决方案中,您可以在此处在线运行:https://play.golang.org/p/CAa54pQs26

package main

import "fmt"

func boundS(ws []int) int {
    best := 5000
    for _, pw := range ws {
        if pw < 0 {
            continue
        }
        for _, nw := range ws {
            if nw > 0 {
                continue
            }
            best = min(best, (pw-nw)/gcd(pw, -nw))
        }
    }
    return best
}

func minSum(ws []int) int {
    maxw := 0
    for _, w := range ws {
        maxw = max(maxw, abs(w))
    }
    N := maxw * boundS(ws)
    n := make([]int, N+1)
    p := make([]int, N+1)
    for i := 1; i <= N; i++ {
        n[i] = 5000
        p[i] = 5000
    }
    for _, w := range ws {
        for i := abs(w); i <= N; i++ {
            if w > 0 {
                p[i] = min(p[i], 1+p[i-w])
            } else {
                n[i] = min(n[i], 1+n[i+w])
            }
        }
    }
    S := p[1] + n[1]
    for i := 1; i <= N; i++ {
        S = min(S, p[i]+n[i])
    }
    return S
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func abs(a int) int {
    if a < 0 {
        return -a
    }
    return a
}

func gcd(a, b int) int {
    if a < b {
        a, b = b, a
    }
    for b > 0 {
        a, b = b, a%b
    }
    return a
}

测试一些简单和一些硬测试用例。代码在我的笔记本电脑上运行不到半秒钟。

func isPrime(p int) bool {
    if p < 4 {
        return p >= 2
    }
    for i := 2; i*i <= p; i++ {
        if p%i == 0 {
            return false
        }
    }
    return true
}

func main() {
    var middle, ends, altPrimes []int
    sign := 1
    for i := -1000; i <= 1000; i++ {
        if i == 0 {
            continue
        }
        if abs(i) <= 500 {
            middle = append(middle, i)
        } else {
            ends = append(ends, i)
        }
        if abs(i) >= 500 && isPrime(i) {
            altPrimes = append(altPrimes, sign*i)
            sign *= -1
        }
    }
    cases := [][]int{
        []int{999, -998},
        []int{10, -11, 15, -3},
        middle,
        ends,
        altPrimes,
    }
    for i, ws := range cases {
        fmt.Println("case", i+1, minSum(ws))
    }
}
相关问题