切割棒使成本最小化

时间:2014-01-14 05:23:26

标签: algorithm

你必须将长度为l的棍子切成几块。必须在c1, c2, c3, ..., cn位置进行剪切,其中ci1n-1之间的整数(包括)。切割的成本等于制作它的棒的长度。削减的顺序应该是什么,以最大限度地降低运营的总成本?

例如,考虑长度为10的长棍,并且必须在位置2, 4, 7进行切割。你可以按照给定的顺序切割木棒。第一次削减将花费10,因为棍子的长度为10。第二次切割将花费8,因为切割所依留的剩余杆的长度为10 - 2 = 8。最后一次削减将花费6,因为剩下的棍子的长度为10 - 4 = 6。总费用为10 + 8 + 6 = 24

但是,如果我们按顺序切割棒:4, 2, 7,我们会得到10 + 4 + 6 = 20的成本,这对我们来说更好。

设计算法来解决问题。

我很确定这是一个DP问题。我能看到的一个诱人的复发关系是,如果我们切一根棍子,我们会得到两根小棍子。如果我们知道这两种棒的最佳解决方案,我们可以很容易地找出更大棒的最佳解决方案。但这样效率很低。

如果你有一个递归函数min_cost(stick_length, c_1, c_2, ..., c_n),它返回在stick_length处剪切长度c_1, c_2, ..., c_n的最小成本,则递归关系看起来像这样

min_cost(stick_length, c_1, c_2, ..., c_n) =
    stick_length 
    + minimum(min_cost(c_1, a_1, a_2, ..., a_i) 
    + min_cost (stick_length - c_1, 
                a_(i+1), ..., a_(n-1)),
                min_cost(c_2, a_1, a_2, ..., a_i) 
    + min_cost(stick_length - c_2, 
               a_(i+1), ..., a_(n-1)), ... , 
               min_cost(c_n, a_1, a_2, ..., a_i)
    + min_cost(stick_length - c_n,
                a_(i+1), ..., a_(n-1)))`,

其中a_1, a_2, ..., a_n是要删除的剩余地方的排列。我们必须将所有可能的排列传递给递归函数,而不仅仅是我写过的那个。

这显然是不切实际的。我该如何解决这个问题?

5 个答案:

答案 0 :(得分:4)

另一个DP解决方案:

让COST(a,b)成为切割第a个和第b个切割点之间的最佳成本。很明显,COST(a,a)和COST(a,a + 1)为零。我们可以计算COST(a,b)的最佳值,作为通过所有中间点a + 1 ... b-1加上自己的段长度的最小值。因此我们可以通过对角线对角填充三角形表,并找到最终结果作为COST(开始,结束),具有O(N ^ 3)时间复杂度和O(N ^ 2)空间

Delphi代码(输出Cost 20 Sequence 4 2 7

var
  Cuts: TArray<Integer>;
  Cost: array of array of Integer;
  CutSequence: array of array of String;
  N, row, col, leftpos, rightpos, cutpos, Sum: Integer;
begin
  Cuts := TArray<Integer>.Create(0, 2, 4, 7, 10); // start, cuts, end points
  N := Length(Cuts);
  SetLength(Cost, N, N);  //zero-initialized 2D array
  SetLength(CutSequence, N, N);  //zero-initialized 2D array

  for rightpos := 2 to N - 1 do
    for leftpos := rightpos - 2 downto 0 do begin //walk along the diagonals
                                                  //using previously computed results
      //find the best (mincost) cut
      Cost[leftpos, rightpos] := MaxInt; //big value
      for cutpos := leftpos + 1 to rightpos - 1 do begin
        Sum := Cost[leftpos, cutpos] + Cost[cutpos, rightpos];
        if Sum < Cost[leftpos, rightpos] then begin
          Cost[leftpos, rightpos] := Sum;
          //write down best sequence
          CutSequence[leftpos, rightpos] := Format('%d %s %s', [Cuts[CutPos],
            CutSequence[leftpos, cutpos], CutSequence[cutpos, rightpos]]);
        end;
      end;

      //add own length
      Cost[leftpos, rightpos] :=
        Cost[leftpos, rightpos] + Cuts[rightpos] - Cuts[leftpos];
    end;

  //show the best result
  Caption := Format('Cost %d  Sequence %s',[Cost[0, N-1], CutSequence[0, N-1]]);

答案 1 :(得分:3)

这实际上是UVa Online Judge的一个问题。 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=944

在这个问题中,L&lt; 1000和n&lt; 50。

正如我在评论中提到的那样,如果你注意到每次切割,可以做出切割的可能长度= n。

总可能remaining lengths应是有限的,对于每个剩余长度,sets of cuts remaining的数量也应是有限的。因此,您可以在剩余的长度上构建DP。

从最小的开始,对于每个“剩余长度”,您可以计算进一步削减它的最低成本。

类似的东西:

DP[k][SetOfCutsRemaining] = k + Min( DP[m1][SetOfCutsRemaining till c1] 
                                 + DP[k-m1][SetOfCutsremaining from c1], 
                                 DP[m2][SetOfCutsRemaining till c2] 
                                 + DP[k-m2][SetOfCutsremaining from c2],... )
                           where mi are the lengths remaining if we make a cut at ci

然后您需要执行此操作直到DP[L][InitialSetOfCuts]

在示例问题中,L = 10, ci = 2, 4, 7

剩余的长度及其相应的切割剩余如下。 请注意,在这种情况下,组合的数量应为C(n + 2,2)=(n + 2)(n + 1)/ 2 = 10

2 {} (2 times, 0-2 and 2-4)
3 {} (2 times, 4-7 and 7-10)
4 {c1}
5 {c2}
6 {c3}
7 {c1, c2}
8 {c2, c3}
10 {c1, c2, c3}

DP[2][{}] = 0 (No cut remaining)
DP[3][{}] = 0 (No cut remaining)
DP[4][{c1}] = 4 (1 cut remaining)
DP[5][{c2}] = 5 (1 cut remaining)
DP[6][{c3}] = 6 (1 cut remaining)
DP[7][{c1,c2}] = 7 + Min( DP[2]{} + DP[5][{c2}], DP[3]{} + DP[4][{c1}] )
               = 7 + Min( 5, 4 ) = 11.
DP[8][{c2,c3}] = 8 + Min( DP[2]{} + DP[6][{c3}], DP[3]{} + DP[5][{c2}] )
               = 8 + Min( 6, 5 ) = 13.
DP[10][{c1,c2,c3}] = 10 + Min( DP[2]{} + DP[8][{c2,c3}], DP[4]{c1} + DP[6][{c3},
                                DP[7][{c1,c2}] + DP[3]{} )
               = 10 + Min( 13, 10, 11 ) = 20.

答案 2 :(得分:2)

首先,假设我们有一个切割位置的升序数组,所以在OP例子中,它将是 {2,4,7}

首先,我们有从0到n的长度,所以我们称之为函数

int cal(int start, int end , int [] cuts)

start = 0和end = n。

对于大于开始且小于结束的每个切割点,我们都有公式

int result = 1000000;
for(int i = 0; i < cuts.length; i++){
   if(cuts[i]> start && cuts[i]<end){
         int val = (end - start) + cal(start, cuts[i], cuts) + cal(cuts[i],end , cuts); 
         result = min(val, result);
   } 
}

DP表可以简单地

dp[start][end]

所以,整个解决方案将是:

int cal(int start, int end, int[]cuts){
    if(dp[start][end]!= -1){//Some initializations need to be done
        return dp[start][end];
    }
    int result = 1000000;
    for(int i = 0; i < cuts.length; i++){
       if(cuts[i]> start && cuts[i]<end){
         int val = (end - start + 1) + cal(start, cuts[i], cuts) + cal(cuts[i],end , cuts); 
         result = min(val, result);
       } 
    }
    return dp[start][end] = result;
}

为了进一步增强空间使用,我们可以将每个切割位置作为其索引引用到数组cuts中。

为切割数组添加了起点和终点,我们有以下数组

{0,2,4,7,10}

通过将起始位置称为索引0,以索引4结束,我们可以将数组dp的空间从dp [10] [10]减小到dp [5] [5]

答案 3 :(得分:0)

对不起,我随时都可以像冰箱一样嗡嗡作响,但用数学说话更是个问题。我可能会为我的生活规范化算法,但只要业力警察让我独自一人,我就不会。

这是我的解决方案(在JavaScript中)。

这是一种纯粹的蛮力方法 没有一个切口(如果我可以这样说),所有分支都被采用。

看来,n次切割的探索切割数量等于3 ^^ n(我确实测量过)。我怀疑对此有一个微不足道的解释,但试图找到它让我头疼,所以......

我使用另一个注释中建议的格式,即数组的最左边和最右边的元素表示当前棒的末端。
例如,[0,2,4,7,10]表示“在0到10的范围内切割位置2,4和7”。

function try_cut_raw (list)
{
    // terminal ends
    if (list.length == 2) return 0;
    if (list.length == 3) return list[2]-list[0];

    // left and right split
    var cost_min = 1e6;
    for (var i = 1 ; i != list.length-1 ; i++)
    {
        var cost = try_cut_raw (list.slice (0, i+1))
                 + try_cut_raw (list.slice (i, list.length));
        if (cost < cost_min) cost_min = cost;
    }
    return cost_min+list[list.length-1]-list[0];
}

更精确的一个,返回要应用的切割的半有序序列以实现结果。

function try_cut (list)
{
    // terminal ends
    if (list.length == 2) return { cost: 0, seq:[] };
    if (list.length == 3) return { cost: list[2]-list[0], seq:[list[1]] };

    // left and right split, retaining best value
    var i_min;
    var cost_min = 1e6;
    var seq_min;
    for (var i = 1 ; i != list.length-1 ; i++)
    {
        var cl = try_cut (list.slice (0, i+1));
        var cr = try_cut (list.slice (i, list.length));

        var cost = cl.cost+cr.cost;
        if (cost < cost_min)
        {
            cost_min = cost;
            // store cut order associated with best result
            seq_min  = [list[i]].concat (cl.seq).concat(cr.seq);
        }
    }
    return { cost: cost_min+list[list.length-1]-list[0], seq: seq_min }
}

带有OP输入的测试用例以及初始质询页面中的两个示例

function cut (list)
{
var cut = try_cut (list);
var cut_raw = try_cut_raw (list);
console.log ("["+list+"] -> "+cut.seq+" cost "+cut.cost+"/"+cut_raw);
}

cut ([0,2,4,7,10]);
cut ([0,25,50,75,100]);
cut ([0,4,5,7,8,10]);

输出

[0,2,4,7,10] -> 4,2,7 cost 20/20
[0,25,50,75,100] -> 50,25,75 cost 200/200
[0,4,5,7,8,10] -> 4,7,5,8 cost 22/22

答案 4 :(得分:0)

我尊重以上所有解决方案,这是我在Java中解决这个问题的方法。

这可能对某人有帮助。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class CutTheSticks2 {
    public static void main(String s[]) throws NumberFormatException, IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        short N = Short.parseShort(br.readLine());
        short[] A = new short[N];
        N = 0;
        for (String str : br.readLine().split(" ")) {
            A[N++] = Short.parseShort(str);
        }

        Arrays.sort(A);

        StringBuffer sb = new StringBuffer();
        System.out.println(N);
        for (int i = 1; i < N; i++) {
            if (A[i - 1] != A[i]) {
                sb.append((N - i) + "\n");
            }
        }

        // OUTPUT
        System.out.print(sb);
    }
}