dp-钢条切割

钢条切割

问题描述

Serling公司购买长钢条,将其切割为短钢条出售。假设切割工序没有成本,不同长度的钢条的售价如下:

length 1 2 3 4 5 6 7 8 9 10
price 1 5 8 9 10 17 17 20 24 30

那么钢条切割问题就是:给定一段长度为n英尺的钢条和一个价格表为pi(i=1...n),求切割钢条方案,使得销售收益最大(单位为元)。注意:如果长度为n英尺的钢条的价格pn足够大,那么最优解就是不需要切割。

问题分析

考虑 n = 4 的情况,那么有以下几种切割方式:
1.切割为四段,长度为:1,1,1,1;总共卖41=4元。
2.切割为三段,长度为:1,1,2;总共卖2
1+15=7元。
3.切割为两段,长度为:1,3;总共卖1
1+18=9元。
4.切割为两段,长度为:2,2;总共卖2
5=10元。
5.不切割,长度为:4;总共卖1*9=9元。

长度为n的钢条,总共有 \(2^{n-1}\) 种不同的切割方案,因为长度为n的钢条,总共有 n-1 个缝隙,每个缝隙都可以选择切或不切,所以有 \(2^{n-1}\) 种不同切割方案。所以随着n增大,切割方案总数呈指数级上升,遍历是不现实的。
在这里,很容易想到,当要分析长度为n的钢条的最优解时,可以先将钢条切成两段。将长度为n的钢条随意切割的方案是 \(2^{n-1}\) 种,但是只切两段的方案只有 n-1 种,这样规避了指数级计算量。
将切成的两段,分别再当作子问题去求解,这就是如下分治策略解法。

思路及简化

自顶向下递归

CUT-ROD(p, n)
  if n == 0
      return 0
  q = -1
  for i = 1 to n
      q = max(q, p[i] + CUT-ROD(p, n-i))
  return q

带备忘录的递归

MEMOIZED-CUT-ROD(p, n)
  let r[0..n] be a new array
  for i = 0 to n
      r[i] = -1
  return MEMOIZED-CUT-ROD-AUX(p, n, r)

MEMOIZED-CUT-ROD-AUX(p, n, r)
  if r[n] >= 0
      return r[n]
  if n == 0
      q = 0
  else
      q = -1
      for i = 1 to n
          q = max(q, p[i] + MEMOIZED-CUT-ROD-AUX(p, n-i, r))
  r[n] = q
  return q

自底向上

BOTTOM-UP-CUT-ROD(p, n)
  let r[0..n] be a new array
  r[0] = 0
  for j = 1 to n
      q = -1
      for i = 1 to j
          q = max(q, p[i] + r[j-i])
      r[j] = q
  return r[n]

程序

#include <iostream>
#include <new>

#define N 10

int solve(const int price[N], int* amount, int* cut, int len);
void print_resolution(const int* amount, const int* cut, int len);

int main(int argc, char** argv) {
    int price[N]{1,5,8,9,10,17,17,20,24,30};

    int len = 50;
    // amount 存放最大金额
    int* amount = new int[len]{0};
    // cut 存放分割长度,print_resolution 中针对每个长度计算了分割方案
    int* cut = new int[len]{0};
    int ret = solve(price, amount, cut, len);
    std::cout << "len: " << len << "; result: " << ret << std::endl;

    print_resolution(amount, cut, len);


    delete[] amount;
    delete[] cut;

    return 0;
}

void print_resolution(const int* amount, const int* cut, int len) {
    // cut 中存放的分割长度不超过10,切割掉cut[i]长度后,剩余的部分需要迭代访问cut[rest]
    //   例:长度23的分割方案 = cut[22] + 剩余 = 3 + cut[22-3] = 3 + cut[19]
    //     = 3 + 10 + cut[19-10] = 3 + 10 + cut[9] = 3 + 10 + 10
    int j, rest;
    for (int i = 0; i < len; ++i) {
        std::cout << "len: " << i+1 << "; amount[i]: " << amount[i]
            << "; cut[i]: " << cut[i] << "\t";
        rest = i + 1;
        while (rest > 0) {
            j = cut[rest-1] == 0 ? rest : cut[rest-1];
            rest -= j;
            std::cout << j << " ";
        }
        std::cout << std::endl;
    }
}

int solve(const int price[N], int* amount, int* cut, int len) {
    int half, sum;
    cut[0] = 0;
    for (int i = 0; i < len; ++i) {
        if (i < N) amount[i] = price[i];
        half = (i+1) / 2;
        for (int j = 1; j <= half; ++j) {
            sum = amount[i-j] + amount[j-1];
            if (amount[i] < sum) {
                amount[i] = sum;
                cut[i] = j;
            }
        }
    }
    return amount[len-1];
}

测试:

$ g++ -o cut cut.cpp && ./cut
len: 50; result: 150
len: 1; amount[i]: 1; cut[i]: 0	1
len: 2; amount[i]: 5; cut[i]: 0	2
len: 3; amount[i]: 8; cut[i]: 0	3
len: 4; amount[i]: 10; cut[i]: 2	2 2
len: 5; amount[i]: 13; cut[i]: 2	2 3
len: 6; amount[i]: 17; cut[i]: 0	6
len: 7; amount[i]: 18; cut[i]: 1	1 6
len: 8; amount[i]: 22; cut[i]: 2	2 6
len: 9; amount[i]: 25; cut[i]: 3	3 6
len: 10; amount[i]: 30; cut[i]: 0	10
len: 11; amount[i]: 31; cut[i]: 1	1 10
len: 12; amount[i]: 35; cut[i]: 2	2 10
len: 13; amount[i]: 38; cut[i]: 3	3 10
len: 14; amount[i]: 40; cut[i]: 2	2 2 10
len: 15; amount[i]: 43; cut[i]: 2	2 3 10
len: 16; amount[i]: 47; cut[i]: 6	6 10
len: 17; amount[i]: 48; cut[i]: 1	1 6 10
len: 18; amount[i]: 52; cut[i]: 2	2 6 10
len: 19; amount[i]: 55; cut[i]: 3	3 6 10
len: 20; amount[i]: 60; cut[i]: 10	10 10
len: 21; amount[i]: 61; cut[i]: 1	1 10 10
len: 22; amount[i]: 65; cut[i]: 2	2 10 10
len: 23; amount[i]: 68; cut[i]: 3	3 10 10
len: 24; amount[i]: 70; cut[i]: 2	2 2 10 10
len: 25; amount[i]: 73; cut[i]: 2	2 3 10 10
len: 26; amount[i]: 77; cut[i]: 6	6 10 10
len: 27; amount[i]: 78; cut[i]: 1	1 6 10 10
len: 28; amount[i]: 82; cut[i]: 2	2 6 10 10
len: 29; amount[i]: 85; cut[i]: 3	3 6 10 10
len: 30; amount[i]: 90; cut[i]: 10	10 10 10
len: 31; amount[i]: 91; cut[i]: 1	1 10 10 10
len: 32; amount[i]: 95; cut[i]: 2	2 10 10 10
len: 33; amount[i]: 98; cut[i]: 3	3 10 10 10
len: 34; amount[i]: 100; cut[i]: 2	2 2 10 10 10
len: 35; amount[i]: 103; cut[i]: 2	2 3 10 10 10
len: 36; amount[i]: 107; cut[i]: 6	6 10 10 10
len: 37; amount[i]: 108; cut[i]: 1	1 6 10 10 10
len: 38; amount[i]: 112; cut[i]: 2	2 6 10 10 10
len: 39; amount[i]: 115; cut[i]: 3	3 6 10 10 10
len: 40; amount[i]: 120; cut[i]: 10	10 10 10 10
len: 41; amount[i]: 121; cut[i]: 1	1 10 10 10 10
len: 42; amount[i]: 125; cut[i]: 2	2 10 10 10 10
len: 43; amount[i]: 128; cut[i]: 3	3 10 10 10 10
len: 44; amount[i]: 130; cut[i]: 2	2 2 10 10 10 10
len: 45; amount[i]: 133; cut[i]: 2	2 3 10 10 10 10
len: 46; amount[i]: 137; cut[i]: 6	6 10 10 10 10
len: 47; amount[i]: 138; cut[i]: 1	1 6 10 10 10 10
len: 48; amount[i]: 142; cut[i]: 2	2 6 10 10 10 10
len: 49; amount[i]: 145; cut[i]: 3	3 6 10 10 10 10
len: 50; amount[i]: 150; cut[i]: 10	10 10 10 10 10
posted @ 2023-08-13 16:35  keep-minding  阅读(19)  评论(0编辑  收藏  举报