最小化权重总和,使加权总和为零
Minimize sum of weights such that weighted sum is zero
给定 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)
来进行动态规划,但那会太慢。如何更快解决?
让我们假设至少有一个正权重和至少一个负权重(否则问题无解)。我们知道 S 最多为 2000,因为如果有权重 -c 和 +d,则 d*-c + c*d = 0。并且由于 c, d <= 1000,我们知道 S(最小正解)位于最多2000个。2000个权重,最大可能总数为200万,最小可能总数为负200万。
现在,我们计算总和为 0 到 200 万的最小正权重数。
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 是任意两个 negative/positive 权重,那么 d/gcd(c, d) * -c + c/gcd(c, d) * d = 0,所以 S 受 min((d+c)/gcd(c, d) 的限制,因为 -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))
}
}
给定 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)
来进行动态规划,但那会太慢。如何更快解决?
让我们假设至少有一个正权重和至少一个负权重(否则问题无解)。我们知道 S 最多为 2000,因为如果有权重 -c 和 +d,则 d*-c + c*d = 0。并且由于 c, d <= 1000,我们知道 S(最小正解)位于最多2000个。2000个权重,最大可能总数为200万,最小可能总数为负200万。
现在,我们计算总和为 0 到 200 万的最小正权重数。
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 是任意两个 negative/positive 权重,那么 d/gcd(c, d) * -c + c/gcd(c, d) * d = 0,所以 S 受 min((d+c)/gcd(c, d) 的限制,因为 -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))
}
}