给定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)
来进行动态编程,但这样做太慢了。如何更快地解决?
答案 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))
}
}