问题:有N本书需要抄写,每本书的页数分别为A[0]...A[N-1],现有K个抄写员。每个抄写员可以连续抄写若干本书,每个抄写员的抄写速度一样一分钟一页,问最少需要多长时间抄写完所有的树。
例:
A = [3,2,4],K = 2
最少时间为:5
分析:若一个抄写员抄写A[i...j],需要时间为A[i]+...+A[j]
最后抄写时间取决于耗时最长的那个抄写员
问题变成,找到一种划分方式,使得分成不超过K段,并且使得最大的那段数字和最小
确定状态:假设在最优策略中最后一个抄写员抄写A[j...N-1],时间为A[j]+...A[N-1]
z则需要知道前K-1个人抄写前j本书从A[0...j-1]的最短时间
子问题:原问题要求K人最短时间抄完前N本书,
现在需要知道K-1人最短时间抄完前j本书
设:f[k][i],表示K人最短时间抄完前i本书,
f[k][i] = min{max{f[k-1][j], A[j]+...+A[i-1]}}(j=0...i)
min{max{K-1人最短时间抄完前j本书; 第K个人抄写从A[j]...到A[i-1]本书所花费的时间 }}
j = i表示第K个人不抄写书
初始条件:f[0][0] = 0,f[0][1]...f[0][N] = sys.maxsize
K(K>0)个抄写员抄写0本书需要最少时间是0
f[K][0] = 0
计算顺序:
f[0][0]...f[0][N]
.
.
.
f[K][0]...f[K][N]
最后答案f[K][N]
时间复杂度:O(N^2*K),空间复杂度O(NK),优化后达到O(N)
若K>N,则令K=N,(每本书最少需要一个人,多出来的人不用抄写)
代码及注释如下:
import sys
def copy_books(A,K):
N = len(A)
if N == 0:
return 0
if K>N:
K= N
f = [[sys.maxsize for i in range(N+1)] for j in range(K+1)]
#初始条件,f[0][0] = 0,f[0][1]...f[0][N] = sys.maxsize
f[0][0] = 0
for k in range(1,K+1):
#抄写员的个数k
f[k][0] = 0
for i in range(1,N+1):
SUM = 0
#K个抄写员抄写前i本书
f[k][i] = sys.maxsize
# #下面这样计算会重复计算,每次都要把A[j]+...+A[i-1]加一遍
# for j in range(0,i+1):
# #f[k][i] = min{max{f[k-1][j], A[j]+...+A[i-1]}}(j=0...i)
# #如果j = i,表示不抄写书,sum(A[j:i])= 0,
# f[k][i] = min(f[k][i],max(f[k-1][j],sum(A[j:i])))
#下面这样计算则不会重复计算,从后往前加,从A[i-1]加到A[j]
for j in range(0,i+1)[::-1]:
#j是最后一个抄写员,抄写A[j...i-1]本书
#求A[j]..A[i-1]从后往前加,避免重复计算
#f[k][i] = min{max{f[k-1][j], A[j]+...+A[i-1]}}(j=0...i)
f[k][i] = min(f[k][i],max(f[k-1][j],SUM))
if j>=0:
SUM += A[j-1]
return f[K][N]
A = [3,2,4]
K = 2
print(copy_books(A,K))
#结果:5