haskell 基础题解(58)

天平称重

【题目】用天平称重时,砝码可以放在左右两个盘中。如果只有1,3,9三个砝码,可以称出13以内的所有整数重量。如果有 1,3,9,27 四个砝码,可以把称重范围扩大到40。
如果只提供这种3倍关系的砝码序列。对任意给定的整数重量 x,求称重方案。
例如: 19 = 27 - 9 + 1

这种问题,如果仔细分析数学规律,当然可以让程序少走弯路。
简单的试算就能发现:当数字 n 在 a,b 两个相邻砝码中间时,到底是表示为 a + …, 还是 b - … 的形式决定于: n < b / 2

如果 n 足够小,就表示为 a + … ,否则就是 b - …
下面的解法,正数负数统一考虑了,有点绕。

counterWeight :: Int -> [Int]
counterWeight x 
    | x == near = [near]
    | abs x <= abs (near `quot` 2) = near `div` 3 : counterWeight (x - near `div` 3) 
    | otherwise = near : counterWeight (x - near) 
    where
        near = signum x * head (dropWhile (< abs x) ma)
        ma = iterate (*3) 1

main :: IO ()
main = do
    print $ counterWeight 4
    print $ counterWeight 19
    print $ counterWeight 14
    print $ counterWeight 23

这里边有个陷阱要注意。
判定near一半的时候,要用 quot,而不能用 div 。因为 div 是向负无穷方向取整,而quot是向零点方向取整。

如果观察不出来这种2分关系也不要紧。可以做如下考虑:
砝码的系列:
1 3 9 27 81 …
它前n项和:
1 4 13 40 121 …

显然,这个和表示前n项所能表示出的最大数字。
比如 10 , 在 13 之内,意味着前3项可表示,最大项9比10小,可以用 9 + …
比如 15, 在 40 之内, 意味着前4项可表示,最大项27比15大,必然是 27 - … 的形式
这样,就可以递归后再符号反转。这就不用考虑负数。

counterWeight :: Int -> [Int]
counterWeight n 
    | n == near = [near]
    | n > near  = near : counterWeight (n - near)  
    | otherwise = near : map (*(-1)) (counterWeight (near - n))
    where
        ma = iterate (*3) 1
        he = scanl1 (+) ma
        near = fst $ head $ dropWhile ((< n) . snd) (zip ma he)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值