AcWing 303. 运输小猫 Python 凸包优化DP,代码真有点难写,比较绕
原题链接
困难
作者:
皓首不倦
,
2020-07-31 23:32:19
,
所有人可见
,
阅读 500
from collections import deque
N, M, P = map(int, input().split())
dis = [0, 0] + list(map(int, input().split()))
for i in range(1, len(dis)):
dis[i] += dis[i-1]
A = []
for i in range(M):
h, t = map(int, input().split())
A.append(t - dis[h])
A.sort()
S = [val for val in A]
for i in range(1, M):
S[i] += S[i-1]
# dp(i, j) 表示前j只猫i个饲养员送的最小开销,等价于把A前j个数分成最多i组,最小的总开销
dp = [[0] * (M+1) for _ in range(P+1)]
for i in range(1, P+1):
if i == 1:
for j in range(M):
dp[i][j] = A[j] * (j + 1) - S[j]
else:
que = deque()
que.append( (-1, 0, -0x7fffffff) ) # (i下标,dp[i][j], 凸包下边界上的斜率)
for j in range(M):
cur_k = A[j]
n = len(que)
l, r = 0, n - 1
pos = -0x7fffffff
while l <= r:
mid = l + (r - l) // 2
if que[mid][2] >= cur_k:
pos = mid
r = mid - 1
else:
l = mid + 1
pos = n - 1 if pos == -0x7fffffff else pos - 1
selected_k = que[pos][2]
k = que[pos][0]
dp[i][j] = S[k] + dp[i-1][k] - A[j]*k - S[j] + j*A[j]
# 更新凸包边界
last_k = 0
if len(que) > 1:
while len(que) > 1:
k = que[-2][0]
last_k = ((S[j]+dp[i-1][j]) - (S[k]+dp[i-1][k])) / (j - k)
if que[-1][2] >= last_k:
que.pop()
else:
break
k = que[-1][0]
last_k = ((S[j] + dp[i-1][j]) - (S[k] + dp[i-1][k])) / (j - k)
que.append((j, dp[i][j], last_k))
# 把队列头的斜率偏小的点删掉,因为这个题目cur_k是递增的,所以斜率偏小的那些点对后续求解已经没作用了
while len(que) > 1 and que[0][2] < selected_k:
que.popleft()
print(dp[P][M-1])