你必须将长度为l
的棍子切成几块。必须在c1, c2, c3, ..., cn
位置进行剪切,其中ci
是1
和n-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
是要删除的剩余地方的排列。我们必须将所有可能的排列传递给递归函数,而不仅仅是我写过的那个。
这显然是不切实际的。我该如何解决这个问题?
答案 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);
}
}