一道算法题:拼数字

题目描述

给定数字5、-5,7,-7,12,-12,每个数字个数无限多,从中选择尽量少的数字,使它们的和为N。

分析

因为对称性,只需要考虑N>0的情况。
因为存在一对互质的正整数(比如5和7),所以总是存在整数x,y使5x+7y=1,只要能够产生1,就一定能够产生任意正整数。因此问题一定有解。

方法一:打表法

自下而上的更新
缺点是比较暴力,占用空间比较大

a = [5, 7, 12, -5, -7, -12]
b = dict()
for i in a:
    b[i] = 1
while 2000 not in b:
    c = dict()
    for i in b:
        for j in a:
            k = i + j
            if k not in b:
                c[k] = b[i] + 1
            elif b[i] + 1 < b[k]:
                b[k] = b[i] + 1
    if len(c) == 0:
        break
    else:
        for i in c:
            b[i] = c[i]
ans = sorted(list(b.items()))
ans = [i for i in ans if i[0] >= 0]
with open("test.txt", "w") as f:
    for i in ans:
        f.write("%d %d
" % (i[0], i[1]))

方法二:循环法

最终答案只有两种选择:
5,-7,12
-5,7,12
其余选择必然不是最优解:选了+5就不能选择-5,选了5和7,就不能选-12(抵消了),5、7同时选必然不如选择一个12。
并且5的个数不会超过7,7的个数不会超过12,否则的话,必然不是最优解。

对于给定的正负数数组a,首先对a进行从小到大排序,a[0]的个数不能超过a[1],a[1]的个数不能超过a[2],a[2]的个数不能超过a[3]......

a = [5, 7, 12, -5, -7, -12]


def f(m, x):
    ans = 0xfffffff
    for i in range(12):
        for j in range(12):
            left = x - m[0] * i - m[1] * j
            if left % m[2] == 0:
                ans = min(ans, i + j + abs(left // m[2]))
    return ans


def solve(x):
    return min(f([5, -7, 12], x), f([-5, 7, 12], x))


def output():
    with open("f1.txt", 'w') as file:
        for i in range(500):
            file.write("%d %d
" % (i, solve(i)))


output()

方法三:方法一和方法二的综合

对于5,7,12三个数字,5的个数肯定不会超过7,7的个数肯定不会超过12。
又因为只有5,-7,12和-7,5,12两种组合,所以有77+30=107
只需要打表出0~107,然后其余部分用12填充即可。

a = [5, 7, 12, -5, -7, -12]
b = dict()
for i in a:
    b[i] = 1
limit=43
while limit*3 not in b:
    c = dict()
    for i in b:
        for j in a:
            k = i + j
            if k not in b:
                c[k] = b[i] + 1
            elif b[i] + 1 < b[k]:
                b[k] = b[i] + 1
    if len(c) == 0:
        break
    else:
        for i in c:
            b[i] = c[i]


def solve(x):
    if x <= limit:
        return b[x]
    cnt = x // 12
    ans = 0xffffff
    while x - cnt * 12 < limit:
        ans = min(ans, cnt + b[x - cnt * 12])
        cnt -= 1
    return ans

经过测试,这个问题中的limit至少为43,这是为什么呢?

更一般的提法

给定正整数数组a[N],从而确定了+a[0],-a[0],+a[1],-a[1]......共2N个整数,从中选择尽量少的数字,使得它们的和为M。
只需打表处理0~a[0]*a[1]+a[1]*a[2]+a[2]*a[3]+a[3]*a[4]范围内的结果,其余部分必定以最大值a[N-1]填充。

原文地址:https://www.cnblogs.com/weiyinfu/p/7779825.html