各位好
運費單位設定為
A 10單位 150
B 5單位 100
C 2單位 60
這三種箱子
以幾個例子來說
單位12的話最划算的結果是A+C = 210
單位15的話最划算的結果是A+B = 250
單位6的話最划算的結果是B+C = 160
不知道程式怎樣寫可以得到這個結果
def maxx(a,b) int = b/a mod = b%a if mod.zero? return int else return int + 1 end end def myrange(a,b) arr = [] maxx = maxx(a,b) 0.step(maxx,1) do |x| arr << x end return arr end def mycount(a,b,c) # ax + by = c puts "#{a}X + #{b}Y = #{c}" myrange = myrange(a,c) myrange.each do |i| #puts "X = #{i}" k = c - i * a mymax = maxx(b,k) if k > 0 puts "#{a} x #{i} + #{b} x #{mymax} = #{c} ( #{a*i + b*mymax} )" else puts "#{a} x #{i} + #{b} x 0 = #{c} ( #{a*i} )" end end end
+--+--+--+ | 5| 2| | +--+--+--+ | 0| 4| 8| +--+--+--+ | 1| 1| 7| +--+--+--+ | 2| 0|10| +--+--+--+
$ irb -r2Box >> mycount(5,2,7) 5X + 2Y = 7 5 x 0 + 2 x 4 = 7 ( 8 ) 5 x 1 + 2 x 1 = 7 ( 7 ) 5 x 2 + 2 x 0 = 7 ( 10 )
chan15提到:
單位6的話最划算的結果是B+C = 160
>> b=BoxPrice.new(10,5,2,6) 10X + 5Y + 2Z = 6 10 x 0 + 5 x 0 + 2 x 3 = 6 ( 6 ) 10 x 0 + 5 x 1 + 2 x 1 = 6 ( 7 ) 10 x 0 + 5 x 2 + 2 x 0 = 6 ( 10 ) 10 x 1 + 5 x 0 + 2 x 0 = 6 ( 10 ) => #<BoxPrice:0x9b064d4 @solutions=[[0, 0, 3], [0, 1, 1], [0, 2, 0], [1, 0, 0]], @prices=[[0, 0, 180], [0, 100, 60], [0, 200, 0], [150, 0, 0]], @total=[180, 160, 200, 150], @min=3, @best=[1, 0, 0]> >> b.best => [1, 0, 0] >> b.total => [180, 160, 200, 150] >> b.prices[b.min] => [150, 0, 0] >> b.solutions[b.min] => [1, 0, 0]
>> b=BoxPrice.new(10,5,2,3) 10X + 5Y + 2Z = 3 10 x 0 + 5 x 0 + 2 x 2 = 3 ( 4 ) 10 x 0 + 5 x 1 + 2 x 0 = 3 ( 5 ) => #<BoxPrice:0x9a07d3c @solutions=[[0, 0, 2], [0, 1, 0]], @prices=[[0, 0, 120], [0, 100, 0]], @total=[120, 100], @min=1, @best=[0, 1, 0]> >> b.best => [0, 1, 0] >> b.total => [120, 100] >> b.prices[b.min] => [0, 100, 0] >> b.solutions[b.min] => [0, 1, 0]
假設 N = 10X + 5Y + 2Z IF N = 0 THEN X = Y = Z = 0 ELSE I = N MOD 10 J = N DIV 10 SELECT CASE I 0: X = J : Y = 0 :Z = 0 1,2: X = J : Y = 0 :Z = 1 3,4,5: X = J : Y = 1 :Z = 0 6,7,8,9: X = J + 1 : Y = 0 : Z = 0 END SELECT END IF
用遞迴來解(抱歉,我用javascript來做):
<pre class="c" name="code">
<script>
var packages = {
a:{unit:10,cost:150},
b:{unit:5,cost:100},
c:{unit:2,cost:60}
};
var res = {};
function compute(p,u,a) {
for(var i in p) {
a.push(i);
if(u>p[i].unit) {
compute(p,u-p[i].unit,a);
} else {
var r = JSON.stringify(a);
r = JSON.parse(r);
var c = 0;
for(var j in r) {
c += p[r[j]].cost;
}
if(res.total) {
if(res.total>c) {
res = {combo:r,total:c};
}
} else {
res = {combo:r,total:c};
}
}
a.pop();
}
}
compute(packages, 15, []);
alert(JSON.stringify(res));
</script>
不過這樣窮舉組合(我這樣已經會排除掉無意義的組合了)的計算次數會隨著單位快速增長,並不適合在需要時動態計算。另外,php做遞迴的效率很差(跟javascript比較的話),所以建議你還是用迴圈展開來做,我只是做好玩的。(其實可以調整一下,在遞迴的條件中加入成本的評估,超過目前最低成本這條路徑就不繼續算下去,這樣除非剛好碰到最差狀況,否則應該可以改善計算的效率,你可以自己試試看。這樣算的時候,packages陣列的順序應該也會影響效率喔...)
高手。
加上評估以後跳過許多不需要的計算,速度會快很多:
<pre class="c" name="code">
<script>
var packages = {
a:{unit:10,cost:150},
b:{unit:5,cost:100},
c:{unit:2,cost:60}
};
var res = {};
function compute(p,u,a) {
for(var i in p) {
a.push(i);
var r = JSON.stringify(a);
r = JSON.parse(r);
var c = 0;
for(var j in r) {
c += p[r[j]].cost;
}
if (res.total && c>res.total) break;
if(u>p[i].unit) {
compute(p,u-p[i].unit,a);
} else {
if(res.total) {
if(res.total>c) {
res = {combo:r,total:c};
}
} else {
res = {combo:r,total:c};
}
}
a.pop();
}
}
compute(packages, 71, []);
alert(JSON.stringify(res));
</script>
其實這個解法有個問題,就是packages裡面元素的順序會影響到計算結果以及複雜度。我應該回來看一下dynamic programming跟圖還有路徑搜尋的演算法資料...
我的想法很直觀,就是把包裝的組合攤開成樹,在建立樹的時候就計算成本,當路徑的成本在某個節點上超過目前已計算過路徑的最低成本時,就跳過這個路徑。
當unit數量大時,用越大的包裝成本越低,所以先計算大的包裝的話,就會更快找到比較低的成本路徑,這樣就可以捨棄掉更多需要計算的路徑,讓計算效率更好。所以packages中包裝方法的順序對於這個計算的方法效率會有影響。不過其實我的方法還是有問題,如果改packages中包裝方法的順序,不是只有計算效率受影響,結果還會不正確,應該要再調整一下....等我有空吧。
另外,遞迴結束的條件(不管捨棄)是在完成包裝時,所以這是深度優先的遍歷。
稍微計算了一下呼叫compute函數的次數來檢驗計算效率。如果有41個單位的物品要包裝,會呼叫8次。但是如果我把:
<pre class="c" name="code"> if (res.total && c>res.total) break;
這一行拿掉,會呼叫21,439次。
正確版:
<pre class="c" name="code">
<script>
var packages = {
c:{unit:2,cost:60},
b:{unit:5,cost:100},
a:{unit:10,cost:150}
};
var res = {};
function compute(p,u,a) {
count++;
for(var i in p) {
a.push(i);
var r = JSON.stringify(a);
r = JSON.parse(r);
var c = 0;
for(var j in r) {
c += p[r[j]].cost;
}
if (res.total && c>res.total) {
a.pop();
continue;
}
if(u>p[i].unit) {
compute(p,u-p[i].unit,a);
} else {
if(res.total) {
if(res.total>c) {
res = {combo:r,total:c};
}
} else {
res = {combo:r,total:c};
}
}
a.pop();
}
}
compute(packages, 41, []);
alert(JSON.stringify(res));
</script>
同樣用41個單位的物品來測試,像上面的程式會呼叫compute函數3,820次,把packages裡面的包裝方法順序反過來的話,會呼叫1,975次。
海綿大,問題答到這樣也只是自娛了,所以ok啦。
另外,建議chan15大用程式跑出lookup table,然後直接代入得出結果就好,這個問題的解決方法時間複雜度大概都在O(N^2),即時算的效率應該不好,除非你確定單位數真的都不大。
應該說O(N^2)以上...殘念,我的方法很爛...
<pre class="c" name="code">
/*
* 傳出陣列(各箱子的數量、金額)
*/
function get_boxnum($num) {
/* 在此設定相關條件
* 一定要將num值由大至小排。要不然會出錯
*/
$boxset = Array('A' => Array('num'=>10,'money'=>150),'B' => Array('num'=>5,'money'=>100),'C' => Array('num'=>2,'money'=>60));
$t_num = $num;
$now_type = Array();
$last_box = '';
foreach($boxset AS $box => $data) {
//計算打包
$tmp_num = floor($t_num/$data['num']);
$now_type[$box]['num'] = $tmp_num;
$now_type[$box]['money'] = $tmp_num*$data['money'];
//取得剩餘數量
$t_num = $t_num%$data['num'];
$last_box = $box; //最後一個箱子編號
}
if($t_num > 0)
{
$now_type[$last_box]['num']++; //如還有剩餘量,將最後一個箱子加1
$now_type[$last_box]['money'] += $boxset[$last_box]['money'];
}
return $now_type;
}
網友提供的寫法,我覺得還不錯,提供給大家
<pre class="c" name="code">$now_type = get_boxnum(6); //直接用買6個做測試
//這裏是顯示。
foreach($now_type AS $tt => $dd) {
echo "使用".$dd['num']."個".$tt."箱子,共".$dd['money']."元\n";
$allmoney +=$dd['money'];
}
echo "合計".$allmoney."元";
應該任何一種程式語言都可以。
這是一個資料結構的最短路徑的問題,你上網 Google 一下應該會有詳細的解說跟範例。
建議找一個 c 語言的例子,然後你再改寫成 php
不過也很可能待會有那位大大,直接就給你答案,也說不定。
我的解法一點也不高深,
只用到數學的四則運算,
較深一點的數學就 +
所以會比較繁瑣些,
幾乎像是畫「九九乘法表」的結構做出來的。
直接把程式碼貼上,
雖然是 Ruby,但還蠻好辨識在寫什麼,
再自行用其他語言來改寫;
篇幅關係,部份推想過程的說明,稍後貼在 [討論](http://ithelp.ithome.com.tw/question/10045138?tab=opini<br />
on#ooa_hash) 中:
<pre class="c" name="code">class BoxPrice
attr_reader :solutions, :every_price, :every_total, :best
def initialize(a,b,c,d)
@solutions = mycount(a,b,c,d)
@prices = every_price(@solutions)
@total = every_total(@prices)
@min = which_min(@total)
@best = @solutions[@min]
end
def maxx(a,b)
# 以 5 * x >= 7 為例算出x 最小的 >= b 的值
# x = 0,1,2
# get the x Max is 2
# here a = 5, b = 7
int = b/a
mod = b%a
if mod.zero?
return int
else
return int + 1
end
end
def myrange(a,b)
# 5 * x >= 7 將可能的x 列舉成陣列
# x = 0,1,2
# 2 is by maxx(a,b)
arr = []
maxx = maxx(a,b)
0.step(maxx,1) do |x|
arr << x
end
return arr
end
def mycount(a,b,c,d)
# 排出x, y, z 的各可能組合
# ax + by + cz = d
puts "#{a}X + #{b}Y + #{c}Z = #{d}"
boxes = []
arange = myrange(a,d)
arange.each do |i|
#puts "X = #{i}"
no_a_total = d - i * a
brange = myrange(b, no_a_total)
brange.each do |j|
no_b_total = no_a_total - j*b
crange = myrange(c, no_b_total)
cmax = maxx(c, no_b_total)
if cmax > 0
puts "#{a} x #{i} + #{b} x #{j} + #{c} x #{cmax} = #{d} ( #{a*i + b*j + c*cmax} )"
boxes << [i, j, cmax]
else
puts "#{a} x #{i} + #{b} x #{j} + #{c} x 0 = #{d} ( #{a*i + b*j} )"
boxes << [i, j, 0]
end
end
end
return boxes
end
def every_price(arr)
# 將各組合換算成價錢的小計
return arr.collect{|gr| [ gr[0] * 150, gr[1] *100, gr[2] * 60]}
end
def every_total(arr)
# 各組價錢的總計
return arr.collect {|gr| sum = 0; gr.each{|x| sum+= x}; sum}
end
def which_min(arr)
# 找出各組價錢的最小值
return arr.index arr.min
end
end
以下是執行的結果:
<pre class="c" name="code">irb -r3Box
>> b=BoxPrice.new(10,5,2,12)
10X + 5Y + 2Z = 12
10 x 0 + 5 x 0 + 2 x 6 = 12 ( 12 )
10 x 0 + 5 x 1 + 2 x 4 = 12 ( 13 )
10 x 0 + 5 x 2 + 2 x 1 = 12 ( 12 )
10 x 0 + 5 x 3 + 2 x 0 = 12 ( 15 )
10 x 1 + 5 x 0 + 2 x 1 = 12 ( 12 )
10 x 1 + 5 x 1 + 2 x 0 = 12 ( 15 )
=> #<BoxPrice:0x8cef570 @solutions=[[0, 0, 6], [0, 1, 4], [0, 2, 1], [0, 3, 0], [1, 0, 1], [1, 1, 0]], @prices=[[0, 0, 360], [0, 100, 240], [0, 200, 60], [0, 300, 0], [150, 0, 60], [150, 100, 0]], @total=[360, 340, 260, 300, 210, 250], @min=4, @best=[1, 0, 1]>
>> b.best
=> [1, 0, 1]
上述可用 b.solutions, b.prices, b.total 來分別列出
各組可能解,各組解的小計價格,各組解的總價。