基础算法整理(十)

转载请注明出处:https://hts0000.github.io/

欢迎与我联系:hts_0000@sina.com

背包问题

有 N 件物品和一个容量为 V 的背包。物品可能多有个属性——费用Ci、价值Wi、数量Si。求解将哪些物品装入背包可使价值总和最大。

  • 01背包:每个物品只有一个
  • 完全背包:每个物品有无限个
  • 多重背包:每个物品有给定数量
  • 分组背包:有N组物品,每组物品中只能选一个物品

01背包

01背包朴素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 1010

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    
    // 存储第 i 件物品的体积和价值
    v, w [N]int
    // dp 数组,第一维表示物品,第二维表示容量
    // f[i][j] 表示把物品 i 放进容量为 j 的背包的最大价值
    f [N][N]int
    // n 是物品的个数,m 是背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    // 处理输入
    fmt.Fscan(in, &n, &m)
    for i := 1; i <= n; i++ { fmt.Fscan(in, &v[i], &w[i]) }
    
    // 初始化,表示0件物品放进容量为 i 的背包里的最大价值为0
    for i := 0; i <= m; i++ { f[0][i] = 0 }
    
    // i 枚举物品
    for i := 1; i <= n; i++ {
        // j 枚举容量
        for j := 1; j <= m; j++ {
            // 如果背包容量放不下物品 i 了,最大价值等于不放物品 i 的最大价值
            if j < v[i] {
                f[i][j] = f[i-1][j]
            // 能放下物品 i,则比较放与不放哪种价值大
            // f[i-1][j] 表示不放物品 i 的最大价值
            // f[i-1][j-v[i]] + w[i] 表示放物品 i 的最大价值
            } else {
                f[i][j] = max(f[i-1][j], f[i-1][j-v[i]] + w[i])
            }
        }
    }
    
    fmt.Fprintln(out, f[n][m])
}

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

01背包优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 1010

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    
    // 存储第 i 件物品的体积和价值
    v, w [N]int
    // dp 数组
    f [N]int
    // n 是物品的个数,m 是背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    // 处理输入
    fmt.Fscan(in, &n, &m)
    for i := 1; i <= n; i++ { fmt.Fscan(in, &v[i], &w[i]) }
    
    // 初始化,表示0件物品放进容量为 i 的背包里的最大价值为0
    for i := 0; i <= m; i++ { f[i] = 0 }
    
    // i 枚举物品
    for i := 1; i <= n; i++ {
        // j 枚举容量
        for j := m; j >= v[i]; j-- {
            // f[j] 表示不选第 i 件物品的最大价值,f[j-v[i]] + w[i] 表示选择第 i 件物品的最大价值
            f[j] = max(f[j], f[j-v[i]] + w[i])
        }
    }
    
    fmt.Fprintln(out, f[m])
}

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

经典模板题

2. 01背包问题

完全背包

完全背包朴素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 1010

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    // 存储第 i 件物品的体积和价值
    v, w [N]int
    // dp 数组,第一维表示物品,第二维表示容量
    f [N][N]int
    // n 是物品的数量,m 是背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    fmt.Fscan(in, &n, &m)
    
    for i := 1; i <= n; i++ { fmt.Fscan(in, &v[i], &w[i]) }
    
    // 枚举物品
    for i := 1; i <= n; i++ {
        // 枚举容量
        for j := 1; j <= m; j++ {
            // 枚举物品个数
            for k := 0; k * v[i] <= j; k++ {
                // f[i][j] 表示第 i 个物品取 k 个放进容量为 j 的背包中的最大价值
                f[i][j] = max(f[i][j], f[i-1][j-v[i]*k] + w[i]*k)
            }
        }
    }
    
    fmt.Fprintln(out, f[n][m])
}

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

完全背包优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 1010

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    // 存储第 i 件物品的体积和价值
    v, w [N]int
    // dp 数组
    f [N]int
    // n 是物品的数量,m 是背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    fmt.Fscan(in, &n, &m)
    
    for i := 1; i <= n; i++ { fmt.Fscan(in, &v[i], &w[i]) }
    
    for i := 1; i <= n; i++ {
        for j := v[i]; j <= m; j++ {
            f[j] = max(f[j], f[j-v[i]] + w[i])
        }
    }
    
    fmt.Fprintln(out, f[m])
}

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

经典模板题

3. 完全背包问题

多重背包

多重背包朴素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 110

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    // 存储第 i 件物品的体积、价值和数量
    v, w, s [N]int
    // dp 数组,
    // f[i][j] 表示数量 s[i] 的物品 i,存入容量为 j 的背包中的最大价值
    f [N][N]int
    // n 是物品的数量,m 是背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    fmt.Fscan(in, &n, &m)
    
    for i := 1; i <= n; i++ { fmt.Fscan(in, &v[i], &w[i], &s[i]) }
    
    // 枚举物品
    for i := 1; i <= n; i++ {
        // 枚举容量
        for j := 1; j <= m; j++ {
            // 枚举物品数量从 0 ~ s[i],且总体积不超过 j
            for k := 0; k <= s[i] && k * v[i] <= j; k++ {
                // f[i][j] 表示第 i 个物品取 k 个放进容量为 j 的背包中的最大价值,0 <= k <= s[i]
                f[i][j] = max(f[i][j], f[i-1][j-v[i]*k] + w[i]*k)
            }
        }
    }
    
    fmt.Fprintln(out, f[n][m])
}

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

多重背包优化

多重背包二进制优化
把物品 i 的数量 s[i],按照二进制分成若干个,$2^0 + 2^1 + … + 2^{k} + s[i] - 2^{k} = s[i]$,将拆分的每一个数看成一个单独的物品。把所有 s[i] 拆分之后,对所有拆分的物品求一次01背包问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
    "bufio"
    "fmt"
    "os"
)

// 物品个数 N <= 1000
// 物品数量 S <= 2000
// 每个物品都按照二进制拆分成了 logS 个
// 所以总的物品个数是 N * logS 上取整
const N int = 25010

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    // 存储第 i 件物品的体积、价值和数量
    v, w [N]int
    // dp 数组,
    // f[i][j] 表示数量 s[i] 的物品 i,存入容量为 j 的背包中的最大价值
    f [N]int
    // n 是物品的数量,m 是背包容量
    n, m int
)

func main() {
    defer out.Flush()

    fmt.Fscan(in, &n, &m)

    // 将物品 i 的数量按照二进制拆成若干个
    cnt := 0
    for i := 0; i < n; i++ {
        var a, b, s int
        fmt.Fscan(in, &a, &b, &s)
        k := 1
        // 二进制拆分
        for k <= s {
            cnt++
            v[cnt] = a * k
            w[cnt] = b * k
            s -= k
            k *= 2
        }
        // 2^k 次方是小于 s[i] 的最大二进制数,剩下的数为 s[i] - 2^k
        if s > 0 {
            cnt++
            v[cnt] = a * s
            w[cnt] = b * s
        }
    }
    // 物品个数为拆分了多少个
    n = cnt
    // 对所有拆分的物品做01背包
    for i := 1; i <= n; i++ {
        for j := m; j >= v[i]; j-- {
            f[j] = max(f[j], f[j-v[i]] + w[i])
        }
    }
    fmt.Fprintln(out, f[m])
}

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

经典模板题

4. 多重背包问题 I
5. 多重背包问题 II

分组背包

分组背包朴素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 110

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    // v[i][j] 表示第 i 组里的物品 j 的体积是多少
    v, w [N][N]int
    // s[i] 表示第 i 组里有多少种物品
    s [N]int
    // f[i][j] 表示把每 i 组物品选一个,放进容量为 j 的背包中的最大价值
    f [N][N]int
    // 物品组数和背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    fmt.Fscan(in, &n, &m)
    
    // 读取输入
    for i := 1; i <= n; i++ {
        // 第 i 组有多少种物品
        fmt.Fscan(in, &s[i])
        // 第 i 组中每个物品的体积和价值
        for j := 1; j <= s[i]; j++ {
            fmt.Fscan(in, &v[i][j], &w[i][j])
        }
    }
    
    // 枚举物品组
    for i := 1; i <= n; i++ {
        // 枚举容量
        for j := 0; j <= m; j++ {
            // 不选第 i 组的最大价值
            f[i][j] = f[i-1][j]
            // 枚举物品组中每一个物品
            for k := 1; k <= s[i] ; k++ {
                // 容量能够放下第 i 组的物品 k
                if v[i][k] <= j {
                    // 第 i 组选择物品 k 放入容量为 j 的背包中的最大价值
                    f[i][j] = max(f[i][j], f[i-1][j-v[i][k]] + w[i][k])
                }
            }
        }
    }
    
    fmt.Fprintln(out, f[n][m])
}

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

分组背包优化

只优化了空间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package main

import (
    "bufio"
    "fmt"
    "os"
)

const N int = 110

var (
    in = bufio.NewReader(os.Stdin)
    out = bufio.NewWriter(os.Stdout)
    // v[i][j] 表示第 i 组里的物品 j 的体积是多少
    v, w [N][N]int
    // s[i] 表示第 i 组里有多少种物品
    s [N]int
    // f[j] 表示把每一组物品各选一个,放进容量为 j 的背包中的最大价值
    f [N]int
    // 物品组数和背包容量
    n, m int
)

func main() {
    defer out.Flush()
    
    fmt.Fscan(in, &n, &m)
    
    // 读取输入
    for i := 1; i <= n; i++ {
        // 第 i 组有多少种物品
        fmt.Fscan(in, &s[i])
        // 第 i 组中每个物品的体积和价值
        for j := 1; j <= s[i]; j++ {
            fmt.Fscan(in, &v[i][j], &w[i][j])
        }
    }
    
    // 枚举物品组
    for i := 1; i <= n; i++ {
        // 枚举容量
        for j := m; j >= 0; j-- {
            // 枚举物品组中每一个物品
            for k := 1; k <= s[i] ; k++ {
                // 容量能够放下第 i 组的物品 k
                if v[i][k] <= j {
                    // 第 i 组选择物品 k 放入容量为 j 的背包中的最大价值
                    f[j] = max(f[j], f[j-v[i][k]] + w[i][k])
                }
            }
        }
    }
    
    fmt.Fprintln(out, f[m])
}

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

经典模板题

9. 分组背包问题