Python 削减成本算法优化

Python 削减成本算法优化,python,algorithm,python-3.x,Python,Algorithm,Python 3.x,我有一张木板,木板上有N个标记。现在我必须切割木板上的所有标记,以使切割所有标记的成本最小。现在假设我首先切割第I个标记,然后使用两个乘数a和b给出成本,这两个乘数是输入,成本是a*左+b*右,其中左和右是剩余部分的大小切割后的木材。例如,如果我有一个长度为10且a=3和b=4的木材,如果我有ex的标记列表:[1,3,5,7,10]所以我不能切割第一个和最后一个标记,因为它们是木材的起点和终点,所以假设我先从标记7开始,那么切割成本将是3*7-1+4*10-7=18+12=30,现在我要的木材是

我有一张木板,木板上有N个标记。现在我必须切割木板上的所有标记,以使切割所有标记的成本最小。现在假设我首先切割第I个标记,然后使用两个乘数a和b给出成本,这两个乘数是输入,成本是a*左+b*右,其中左和右是剩余部分的大小切割后的木材。例如,如果我有一个长度为10且a=3和b=4的木材,如果我有ex的标记列表:[1,3,5,7,10]所以我不能切割第一个和最后一个标记,因为它们是木材的起点和终点,所以假设我先从标记7开始,那么切割成本将是3*7-1+4*10-7=18+12=30,现在我要的木材是一个从标记1到标记7的木材,另一个是一个从标记7到木材末尾的木材,我会重复这个过程直到所有的痕迹都被剪掉了

现在,在阅读了这个问题之后,我立刻想到,为了以最低的成本切割木材,我首先需要找到切割点的中点,在那里我应该切割木材,并反复重复这个过程,直到木材没有剩下的切割点,但我在解决切割后获得的右侧木材时遇到了问题

样本输入:用[1,3,5,9,16,22]切割的木材,当我们第一次从中间值9开始时,最低成本为163,然后我们将得到标记为[1,3,5,9]和[9,16,22]的木材,现在首先解决左边的木材,我们将得到[1,3,5][5,9],现在再次切割我们得到[1,3][3,5][5,9]和剩下的[9,16,22]现在,在操作这种木材时,我们已经删除了所有标记,列表将是[1,3][3,5][5,9][9,16][16,22],并且此操作的成本将是最低的

这是我的密码:

for _ in range(int(input())):     #no of test cases 
a,b=map(int,input().split())      #multiplier element of cost ex: 3,4
n=int(input())                    #total number of cuts ex: 6
x=list(map(int,input().split()))  #where the cuts are the wood ex:
                                  #[1,3,5,9,16,22]

lis=[]
cost=0

average=sum(x)/len(x)
median=min(x,key=lambda X:abs(X-average))  #calculated the median 

cost+=a*(median-x[0])+b*(x[-1]-median)  #calculated the cost of cutting 
print(cost)
var=x.index(median)                      #found the index of median
x_left=x[:var+1]                         #split the wood in left and right
x_right=x[var:]
lis.append(x_right)
while(len(x_left)>2):       #done the same process going first on left wood    

    average=sum(x_left)/len(x_left)
    median=min(x_left,key=lambda X:abs(X-average))
    cost+=a*(median-x_left[0])+b*(x_left[-1]-median)
    var=x.index(median)
    x_left=x_left[:var+1]
    x_right=x_left[var:] #this wood would again have right component so 
                         #stored that right side in list named lis
    lis.append(x_right)
print(cost)             #fully solved by moving leftwards
print(lis)
tt=len(lis)
for i in lis:           #but the problem start here i have all the right 
                        #pieces of wood that i had stored in lis but now i 
                        #can't evaluate them
    if(len(i)<3):
        lis.pop(lis.index(i))


    else:
        continue
print(lis)
while(tt!=0):
    xx=lis.pop(0)
    ttt=len(xx)
    if(ttt>2):
        average=sum(xx)/ttt
        median=min(xx,key=lambda X:abs(X-average))
        cost+=a*(median-xx[0])+b*(xx[-1]-median)
        var=x.index(median)
        x_left=xx[:var+1]
        x_right=xx[var:]
        if(len(x_left)>2):
            lis.append(x_left)
        if(len(x_right)>2):
            lis.append(x_right)
print(cost)

这个问题需要函数和递归。你想要的是这样的:

function total_cost(a, b, marks):
    # Do something clever here
现在你的分数不到3分,问题就简单了。不需要削减,成本为0

function total_cost(a, b, marks):
    if len(marks) < 3:
        return 0
    else
        # Do something clever here
如果你有两个以上的分数,在任何一个特定的地方切割的成本就是在那里切割的成本,加上切割其余部分的成本。因此,切割成本是最低成本或这些成本。这应该足以填补一些聪明的东西


此解决方案将在多次切割时缓慢运行。要解决这个问题,你应该查阅备忘录。

这个问题需要函数和递归。你想要的是这样的:

function total_cost(a, b, marks):
    # Do something clever here
现在你的分数不到3分,问题就简单了。不需要削减,成本为0

function total_cost(a, b, marks):
    if len(marks) < 3:
        return 0
    else
        # Do something clever here
如果你有两个以上的分数,在任何一个特定的地方切割的成本就是在那里切割的成本,加上切割其余部分的成本。因此,切割成本是最低成本或这些成本。这应该足以填补一些聪明的东西


此解决方案将在多次切割时缓慢运行。要解决这个问题,您应该查找memonization。

首先,这里有一个递归生成器solve\u gen,它检查所有可能的切割顺序,然后选择最小的切割顺序。虽然代码很紧凑,如果标记的数量很小,它运行正常,但随着标记数量的增加,它很快就会变得相当低效。我还包括了一个函数apply_cuts,它将一系列剪切应用于标记序列,因此您可以看到按照该顺序发生的剪切

solve_gen使用全局计数来跟踪所进行的递归调用的数量。计数对于算法的操作不是必需的,但它可以指示函数正在做多少工作

def cost(seq, m):
    return (seq[-1] - seq[0]) * m

def solve_gen(seq):
    global count
    count += 1
    if len(seq) == 2:
        yield 0, ()
        return
    for i in range(1, len(seq)-1):
        left, x, right = seq[:i+1], seq[i], seq[i:]
        val = cost(left, a) + cost(right, b)
        for lval, lcuts in solve_gen(left):
            for rval, rcuts in solve_gen(right):
                yield (val + lval + rval, (x,) + lcuts + rcuts)

def apply_cuts(seq, cuts):
    total = 0
    old = [seq]
    for x in cuts:
        new = []
        for u in old:
            if x in u:
                i = u.index(x)
                left, right = u[:i+1], u[i:]
                val = cost(left, a) + cost(right, b)
                new.extend((left, right))
            else:
                new.append(u)
        print(x, new, val)
        total += val
        old = new[:]
    return total

# Test

# Recursion counter
count = 0

a, b = 3, 4
seq = (1, 3, 5, 9, 16, 22)
print(seq)

results = list(solve_gen(seq))
val, cuts = min(results)
print('Cost: {}, Cuts: {}'.format(val, cuts))
print('Results length: {}, Count: {}'.format(len(results), count))

print('\nCutting sequence')
print(apply_cuts(seq, cuts))
输出

FWIW,以下是相同a&b和较长标记序列的结果

(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Results length: 4862, Count: 41990
(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Count: 55

cost cache CacheInfo(hits=240, misses=90, maxsize=None, currsize=90)
solve cache CacheInfo(hits=276, misses=55, maxsize=None, currsize=55)
我们可以通过在递归的每个阶段找到极小值,而不是找到所有可能性的极小值,来提高效率。然而,当算法研究各种切割选项时,它经常重复以前的计算。因此,我们可以通过使用缓存使代码更加高效,也就是说,我们将以前的结果存储在字典中,因此,如果我们需要再次进行相同的剪切,我们可以在缓存中查找它,而不是重新计算它

我们可以编写自己的缓存,但是functools模块提供了可以用作装饰器的代码。我们也可以给代价函数一个缓存,尽管它的计算相当简单,所以缓存可能不会在那里节省很多时间。lru_缓存的一个很好的特性是它还可以提供缓存统计信息,这让我们知道缓存有多有用

from functools import lru_cache

@lru_cache(None)
def cost(seq, m):
    return (seq[-1] - seq[0]) * m

@lru_cache(None)
def solve(seq):
    global count
    count += 1
    if len(seq) == 2:
        return 0, ()
    results = []
    for i in range(1, len(seq)-1):
        left, x, right = seq[:i+1], seq[i], seq[i:]
        val = cost(left, a) + cost(right, b)
        lval, lcuts = solve(left)
        rval, rcuts = solve(right)
        results.append((val + lval + rval, (x,) + lcuts + rcuts))
    return min(results)

# Test

# Recursion counter
count = 0

a, b = 3, 4
seq = (1, 3, 5, 9, 16, 22)
print(seq)

val, cuts = solve(seq)
print('Cost: {}, Cuts: {}'.format(val, cuts))
print('Count: {}\n'.format(count))

print('cost cache', cost.cache_info())
print('solve cache', solve.cache_info())
输出

幸运的是,我们得到了和以前一样的结果;请注意,递归计数现在要低得多。让我们用更长的标记序列来试试

(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Results length: 4862, Count: 41990
(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Count: 55

cost cache CacheInfo(hits=240, misses=90, maxsize=None, currsize=90)
solve cache CacheInfo(hits=276, misses=55, maxsize=None, currsize=55)
与之前的41990相比,递归计数仅为55;大幅减少。我们可以看到,缓存正在得到很好的利用


FWIW,所有可能的切割序列的数量由在组合问题中经常出现的给出。

首先,这里是一个递归生成器solve\u gen,它检查所有可能的切割序列 然后选择最小的一个。虽然代码很紧凑,如果标记的数量很小,它运行正常,但随着标记数量的增加,它很快就会变得相当低效。我还包括了一个函数apply_cuts,它将一系列剪切应用于标记序列,因此您可以看到按照该顺序发生的剪切

solve_gen使用全局计数来跟踪所进行的递归调用的数量。计数对于算法的操作不是必需的,但它可以指示函数正在做多少工作

def cost(seq, m):
    return (seq[-1] - seq[0]) * m

def solve_gen(seq):
    global count
    count += 1
    if len(seq) == 2:
        yield 0, ()
        return
    for i in range(1, len(seq)-1):
        left, x, right = seq[:i+1], seq[i], seq[i:]
        val = cost(left, a) + cost(right, b)
        for lval, lcuts in solve_gen(left):
            for rval, rcuts in solve_gen(right):
                yield (val + lval + rval, (x,) + lcuts + rcuts)

def apply_cuts(seq, cuts):
    total = 0
    old = [seq]
    for x in cuts:
        new = []
        for u in old:
            if x in u:
                i = u.index(x)
                left, right = u[:i+1], u[i:]
                val = cost(left, a) + cost(right, b)
                new.extend((left, right))
            else:
                new.append(u)
        print(x, new, val)
        total += val
        old = new[:]
    return total

# Test

# Recursion counter
count = 0

a, b = 3, 4
seq = (1, 3, 5, 9, 16, 22)
print(seq)

results = list(solve_gen(seq))
val, cuts = min(results)
print('Cost: {}, Cuts: {}'.format(val, cuts))
print('Results length: {}, Count: {}'.format(len(results), count))

print('\nCutting sequence')
print(apply_cuts(seq, cuts))
输出

FWIW,以下是相同a&b和较长标记序列的结果

(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Results length: 4862, Count: 41990
(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Count: 55

cost cache CacheInfo(hits=240, misses=90, maxsize=None, currsize=90)
solve cache CacheInfo(hits=276, misses=55, maxsize=None, currsize=55)
我们可以通过在递归的每个阶段找到极小值,而不是找到所有可能性的极小值,来提高效率。然而,当算法研究各种切割选项时,它经常重复以前的计算。因此,我们可以通过使用缓存使代码更加高效,也就是说,我们将以前的结果存储在字典中,因此,如果我们需要再次进行相同的剪切,我们可以在缓存中查找它,而不是重新计算它

我们可以编写自己的缓存,但是functools模块提供了可以用作装饰器的代码。我们也可以给代价函数一个缓存,尽管它的计算相当简单,所以缓存可能不会在那里节省很多时间。lru_缓存的一个很好的特性是它还可以提供缓存统计信息,这让我们知道缓存有多有用

from functools import lru_cache

@lru_cache(None)
def cost(seq, m):
    return (seq[-1] - seq[0]) * m

@lru_cache(None)
def solve(seq):
    global count
    count += 1
    if len(seq) == 2:
        return 0, ()
    results = []
    for i in range(1, len(seq)-1):
        left, x, right = seq[:i+1], seq[i], seq[i:]
        val = cost(left, a) + cost(right, b)
        lval, lcuts = solve(left)
        rval, rcuts = solve(right)
        results.append((val + lval + rval, (x,) + lcuts + rcuts))
    return min(results)

# Test

# Recursion counter
count = 0

a, b = 3, 4
seq = (1, 3, 5, 9, 16, 22)
print(seq)

val, cuts = solve(seq)
print('Cost: {}, Cuts: {}'.format(val, cuts))
print('Count: {}\n'.format(count))

print('cost cache', cost.cache_info())
print('solve cache', solve.cache_info())
输出

幸运的是,我们得到了和以前一样的结果;请注意,递归计数现在要低得多。让我们用更长的标记序列来试试

(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Results length: 4862, Count: 41990
(1, 3, 5, 9, 16, 22, 31, 33, 35, 39, 46)
Cost: 497, Cuts: (31, 16, 9, 5, 3, 22, 39, 35, 33)
Count: 55

cost cache CacheInfo(hits=240, misses=90, maxsize=None, currsize=90)
solve cache CacheInfo(hits=276, misses=55, maxsize=None, currsize=55)
与之前的41990相比,递归计数仅为55;大幅减少。我们可以看到,缓存正在得到很好的利用


FWIW,所有可能的切割序列的数量由在组合问题中经常出现的给定。

这个问题是来自编码竞赛吗?你能提供一个到原始问题的链接吗?你的代码相当混乱,而且开头没有正确缩进。但是,最好用硬编码的数据替换从输入中读取数据的起始部分,当您试图开发代码时,在输入提示下输入数据很烦人。通过使用函数,可以减少代码的混乱,减少重复。为什么你认为在中间带切割会起作用?在某些情况下可能是这样,但一般情况下并非如此。切割点的计算需要考虑a和b。@PM2Ring,因为如果沿着一端的一侧切割木材,剩余部分的尺寸会很大,成本会更高。假设我有一块长度为20的木材,上面有标记[1,3,5,7,10,15,17,20]现在假设我有相同的a和b,那么我能降低成本的最好方法是选择10,因为左端和右端的差异是相同的,如果我想削减15分,虽然削减成本一开始是相同的,那么剩余的成本会更高。@PM2Ring是的,我知道这也取决于a和b,但首先我想解决这个问题,同时考虑a和b是相同的,然后应用if条件。这个问题来自CarlerCup上提供的google面试问题。我的apply_cuts函数对于测试这样的理论很方便。给定a,b=3,4和seq=1,3,5,7,10,15,17,20,cuts=10,5,3,7,15,17是成本为180的最小解决方案。如果我们交换a和b,同样的削减将给出一个最小的解决方案,但是如果我们改变a,b=2,5,那么最小的解决方案是15,10,7,5,3,17,成本是174,但是如果我们应用之前的削减,成本将上升到188。这个问题是来自编码竞赛吗?你能提供一个到原始问题的链接吗?你的代码相当混乱,而且开头没有正确缩进。但是,最好用硬编码的数据替换从输入中读取数据的起始部分,当您试图开发代码时,在输入提示下输入数据很烦人。通过使用函数,可以减少代码的混乱,减少重复。为什么你认为在中间带切割会起作用?在某些情况下可能是这样,但一般情况下并非如此。切割点的计算需要考虑a和b。@PM2Ring,因为如果沿着一端的一侧切割木材,剩余部分的尺寸会很大,成本会更高。假设我有一块长度为20的木材,上面有标记[1,3,5,7,10,15,17,20]现在假设我有相同的a和b,那么我能降低成本的最好方法是选择10,因为左端和右端的差异是相同的,如果我想削减15分,虽然削减成本一开始是相同的,那么剩余的成本会更高。@PM2Ring是的,我知道这也取决于a和b,但首先我想解决这个问题,同时考虑a和b是相同的,然后应用if条件。这位职业选手
这个问题来自CarlerCup上提供的google面试问题。我的apply_cuts函数对于测试这样的理论很方便。给定a,b=3,4和seq=1,3,5,7,10,15,17,20,cuts=10,5,3,7,15,17是成本为180的最小解决方案。如果我们交换a&b,同样的削减将给出一个最小的解决方案,但是如果我们改变a,b=2,5,那么最小的解决方案是15,10,7,5,3,17,成本为174,但是如果我们应用之前的削减,成本将上升到188。非常感谢!我的三个问题都被你回答了。你是救命恩人:@Demonking28我的荣幸!你很幸运,我喜欢组合问题;def costseq,m:return seq[-1]-seq[0]*m这里的'm'是什么?@uitwaa m是成本乘数,所以要得到左件的成本,我们将a作为m参数传递,右件的成本,我们将b作为m参数传递。@PM2Ring请看一看:非常感谢!我的三个问题都被你回答了。你是救命恩人:@Demonking28我的荣幸!你很幸运,我喜欢组合问题;def costseq,m:return seq[-1]-seq[0]*m这里的'm'是什么?@uitwaa m是成本乘数,所以要得到左件的成本,我们将a作为m参数传递,右件的成本,我们将b作为m参数传递。@PM2Ring您能看一下吗: