Thuật toán tìm mệnh giá tiền tối ưu


8

Mark sống ở một đất nước nhỏ bé, những người có xu hướng suy nghĩ quá mức. Một ngày nọ, nhà vua của đất nước quyết định thiết kế lại tiền tệ của đất nước để thay đổi hiệu quả hơn. Nhà vua muốn giảm thiểu số lượng tiền dự kiến ​​cần có để trả chính xác bất kỳ số tiền nào lên tới (nhưng không bao gồm) số tiền của hóa đơn giấy nhỏ nhất.

Giả sử rằng đơn vị tiền tệ nhỏ nhất là Coin. Hóa đơn giấy nhỏ nhất trong vương quốc có giá trị Coins. Nhà vua quyết định rằng không nên có nhiều hơn m mệnh giá tiền xu khác nhau đang lưu hành. Sau đó, vấn đề là tìm m -set { d 1 , d 2 , . . . , d m } số nguyên từ { 1 , 2 , . . . , n - 1 } tối thiểu hóa 1nmm{d1,d2,...,dm}{1,2,...,n1}tuân theoc1(i)d1+c2(i)d2+. . . cm(i)dm=i1n1i=1n1c1(i)+c2(i)+...+cm(i)c1(i)d1+c2(i)d2+...cm(i)dm=i.

Ví dụ: lấy USD tiêu chuẩn và các mệnh giá tiền xu của nó là . Ở đây, hóa đơn giấy nhỏ nhất trị giá 100 đồng xu nhỏ nhất. Phải mất 4 xu để kiếm 46 xu bằng cách sử dụng loại tiền này; ta có c 1 ( 46 ) = 1 , c 2 ( 46 ) = 0 , c 3 ( 46 ) = 2 , c 4 ( 46 ) = 1 , c 5{1,5,10,25,50} . Tuy nhiên, nếu chúng ta có các mệnh giá tiền xu là { 1 , 15 , 30 } , thì sẽ chỉ mất 3 đồng tiền: c 1 ( 46 ) = 1 , c 2 ( 46 ) = 1 , c 3 ( 46 ) = 1 . Những bộ mệnh giá nào giảm thiểu số lượng tiền trung bình để kiếm bất kỳ số tiền nào lên tới và bao gồm 99 xu?c1(46)=1,c2(46)=0,c3(46)=2,c4(46)=1,c5(46)=0{1,15,30}c1(46)=1,c2(46)=1,c3(46)=1

Tổng quát hơn, với m , làm thế nào một thuật toán có thể xác định tập hợp tối ưu? Rõ ràng, người ta có thể liệt kê tất cả các m -sets khả thi và tính toán số lượng tiền trung bình cần có để tạo ra các khoản tiền từ 1 đến n - 1 , theo dõi mức tối ưu trên đường đi. Vì có khoảng C ( n - 1 , m ) m -subets (không phải tất cả đều khả thi, nhưng vẫn còn), nên điều này sẽ không hiệu quả khủng khiếp. Bạn có thể làm tốt hơn thế không?nmmn1C(n1,m) m


Nếu m <n - 1, thì không phải giải pháp sẽ luôn có mệnh giá chính xác m sao? Nếu tôi có giải pháp với k xu cho (k <m <n - 1), tôi luôn có thể giảm một đồng xu cho số đếm> 0 xuống 1 bằng cách thêm một mệnh giá chỉ cho nó, do đó giảm trung bình. Nếu đó là sự thật, thì điều đó có làm giảm thời gian chạy ngây thơ không?
Mike Samuel

@MikeSamuel Chắc chắn. Tuy nhiên, nếu có hai giải pháp tốt như nhau, một có mệnh giá và một có mệnh giá k < m , đó có thể là điều mà nhà vua quan tâm khi biết. Rốt cuộc, việc kiếm được nhiều loại tiền hơn mk<m
Patrick87

Tôi không nghĩ rằng có thể có hai giải pháp tốt như nhau được xác định chỉ bằng cách tính tổng ở trên khi m <n-1. Nếu có một đồng xu có giá trị k trong đó 1 <= k <n, thì phần tử của tổng đó là 1 và nếu không có một đồng xu có giá trị k, thì phần tử của tổng kết đó là> 1.
Mike Samuel

@MikeSamuel Tôi nghĩ điều đó có thể đúng, nhưng một lần nữa, tôi muốn xem đó là một phần của câu trả lời, có thể với một số động lực. Nó thực sự có một chút phức tạp, vì các bộ có thể (hầu hết) không chồng chéo.
Patrick87

Đây là một thực tế khác thu hẹp không gian giải pháp: một đồng xu trị giá 1 phải xuất hiện trong tất cả các giải pháp.
Mike Samuel

Câu trả lời:



2

{bi| b=n1/m,0i<m}{1,3,9,27,81}

390/99420/99


(m,n)=(4,30)75/29{20,8,3,1}87/29{27,9,3,1}(5,100)

Tuy nhiên, tôi nhận thấy rằng lỗi dường như khá nhỏ. Hầu hết thời gian, việc chia các khoản tiền mang lại một cái gì đó bắt đầu bằng 1.0 ..., vì vậy tôi đã chạy thêm một số bài kiểm tra.

3m56n401.120.085

n=100


Đây là bộ thử nghiệm của tôi, nếu bạn muốn dùng thử:

getopt :: [Integer] -> Integer -> [Integer]
getopt _ 0 = []
getopt coins target = choice:(getopt viable $ target - choice)
                          where
                            viable = filter ((>=) target) coins
                            choice = maximum $ viable

getsum :: [Integer] -> Integer -> Int
getsum coins n = sum $ map length $ map (getopt coins) [1..(n-1)]

buildall :: Integer -> Integer -> [[Integer]]
buildall 1 _ = [[1]]
buildall m n = foldl1 (++) $ map (\am -> map (\x -> x:am) [((head am)+1) .. (n-1)]) $ buildall (m-1) n

buildguess :: Integer -> Integer -> [Integer]
buildguess m n = reverse $ map ((^) $ ceiling $ (fromInteger n)**(1.0/(fromInteger m))) [0..(m-1)]

findopt :: Integer -> Integer -> ([Integer],Int)
findopt m n = foldl1 (\(l@(_,lhs)) -> (\(r@(_,rhs)) -> (if (lhs < rhs) then l else r)))
            $ map (\arr -> (arr,getsum arr n)) $ buildall m n

intcast :: (Integral a,Num b) => a -> b
intcast = fromInteger.toInteger

esterror :: Integer -> Integer -> Double
esterror m n = (intcast $ getsum (buildguess m n) n) / (intcast best)
                 where (_,best) = findopt m n

Tôi đã chạy thử nghiệm với

map (uncurry esterror) [(m,n) | m <- [3..5], n <- [6..40] ]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.