[CSP]食材运输python3满分详细题解
前言
最近准备算法比赛,感觉这个食材运输收获挺多,特别来记录一下满分的题解和思路过程,希望可以帮助到需要的小伙伴~
原题在这里我就不贴了吧先。
一、解题思路
总的来说,这道题目是先给我一个树的结构,该树中有n个节点和n-1条边并且边上的权重表示的是此条路径花费的时间,总共k种食材,每个节点对于食材的的需求可能不同,每个节点也可以去需要多种食材。要求我们从不同的m个点同时出发,满足尽可能短的时间将所有的食材送到相应的节点处。输出花费的最短时间
刚看这个题目时候,很多人可能立刻就懵了,因为这里边的变量挺多。不同的食材数目k,不同的出发点数目m,再加上复杂的树的结构使好多人对这个题目望而却步。冷静分析之后,我们采用自顶向下的思路解题:
- 我们计从最多m个节点出发,满足k种食材的时间花费为dp[m][k],其中k用二进制位运算进行表示。例如,第一种食材就是
0b01
,只包含第二种食材就是0b10
,包含第一、第二种食材就是0b11
。那么我们如何计算dp[m][k]的值呢?其实这是一个动态规划的问题,我们不妨先计算出dp[1][k],即只从一个节点出发后送完所有食材的所有策略的最小值,dp[1][k]的计算一会讨论。我们可以发现dp的状态转移方程:设s’为s的一个子集,我们对以下公式枚举每一个s’
d p [ i ] [ s ] = m i n { d p [ i ] [ s ] , m a x { d p [ 1 ] [ s ′ ] , d p [ i − 1 ] [ s − s ′ ] } } dp[i][s]=min_\{dp[i][s],max\{dp[1][s'],dp[i-1][s-s']\}\} dp[i][s]=min{dp[i][s],max{dp[1][s′],dp[i−1][s−s′]}}所以可以发现,我们只需要求出dp[1][s’],就可以通过动态规划得到最终的答案 - 求dp[1][s’]:对于某个特定的食材需求组合s’,求某从一个顶点出发配送完成的最短时间。我们不妨假设从顶点
i
出发送组合为s'
的时间花费为g[i][s']
,显然由dp的意义有
d p [ 1 ] [ s ′ ] = m i n 1 ≥ i ≥ n { g [ i ] [ s ′ ] } dp[1][s']=min_{1\geq i \geq n}\{g[i][s']\} dp[1][s′]=min1≥i≥n{g[i][s′]} - 求g[i][s’]: 比如我们原先知道了送第一种+第二种(即k为0b11)花费的时间为3分钟,单独送第三种(0b100)花费的时间为5分钟,那么送第一种+第二种+第三种花费的时间为多少呢?显然有
g [ i ] [ 0 b 111 ] = m a x { g [ i ] [ 0 b 11 ] , g [ i ] [ 0 b 100 ] } g[i][0b111]=max\{g[i][0b11],g[i][0b100]\} g[i][0b111]=max{g[i][0b11],g[i][0b100]},从以上公式可以看出,g的计算又是一个状态转移方程,即动态规划,需要求解的是确定特定出发点和某个单独食材需求,所花费的最短配送时间。
4.求从某特定点出发配送某一种单独食材的最短时间花费。这里有个结论:从出发点出发,除了到达最远节点的路径走了一遍之外,其余的路径皆经过两遍
,所以,2*(出发点到所有节点路径和-出发点到最远节点路径)
,就是第4个小问题的解
二、代码
class Edge:
def __init__(self,to=1,w=0):
self.to=to
self.w=w
self.source_edge=-1
class Solution:
def foodTrans(self,n,m,k,need,edges):
def addEdge(u,v):
nonlocal cnt
nonlocal pre
nonlocal edge_list
edge_list[cnt].to=v
edge_list[cnt].source_edge=pre[u]
pre[u]=cnt
cnt+=1
#表示从第start个节点出发,到达所有p[i]为1的节点的距离之和
def getSum(start):
nonlocal used
num = pre[start]
q=edge_list[pre[start]]
res=0
#讨论到单点
if q.source_edge==-1:
if not used[num//2]:
used[num//2]=True
res_flag=getSum(q.to)
res+=res_flag
if p[q.to]==1 or res_flag!=0:
res+=q.w
used[num//2]=False
return res
else:
return 0
while num!=-1:
q=edge_list[num]
if not used[num//2]:
used[num//2]=True
res_flag=getSum(q.to)
res+=res_flag
if p[q.to]!=0 or res_flag!=0:
res+=q.w
used[num//2]=False
num=q.source_edge
return res
#表示从第i个节点出发,得到的最长距离
def getLongest(start):
nonlocal used
res=0
num=pre[start]
q=edge_list[num]
if q.source_edge==-1:
if not used[num//2]:
used[num//2]=True
res_flag=getLongest(q.to)
used[num//2]=False
if p[q.to]==1 or res_flag!=0:
return res_flag+q.w
else:
return 0
while num!=-1:
q=edge_list[num]
if not used[num//2]:
used[num//2]=True
temp=getLongest(q.to)
if p[q.to]==1 or temp!=0:
temp+=q.w
res=max(res,temp)
used[num//2]=False
num=q.source_edge
return res
#1.g[v][u]表示从结点v开始出发,遍历所有u的子集的最短路径,min(g[v][S]),先遍历S,再遍历v。先找S最大的,再找v最小的
# dp[1,T]=min(g[v][T])
def calc(now):
nonlocal p
p=[0 for _ in range(n+1)]
for i in range(1,n+1):
if need[i]&(1<<now):
p[i]=1
for i in range(1,n+1):
g[i][1<<now]=(getSum(i)<<1) -getLongest(i)
for i in range(1<<now):
dp[1][i+(1<<now)]=float('inf')
for j in range(1,n+1,1):
g[j][i+(1<<now)]=max(g[j][i],g[j][1<<now])
dp[1][i+(1<<now)]=min(dp[1][i+(1<<now)],g[j][i+(1<<now)])
dp=[[0 for _ in range(1<<(k+1))] for _ in range(m+1)]
p=[0 for _ in range(n+1)]
g=[[0 for _ in range(1<<(k+1))] for _ in range(n+1)]
used=[False]*(n-1)
edge_list=list()
cnt=0
pre=[-1]*(n+1)
for x,y,w in edges:
edge_list.append(Edge(y,w))
addEdge(x,y)
edge_list.append(Edge(x,w))
addEdge(y,x)
for i in range(k):
calc(i)
for j in range(0,1<<k):
for i in range(2,m+1):
dp[i][j]=dp[1][j]
s=j
while s:
dp[i][j]=min(dp[i][j],max(dp[1][j-s],dp[i-1][s]))
s=(s-1)&j
return dp[m][(1<<k)-1]
if __name__=='__main__':
solution=Solution
n,m,k=map(int,input().split())
need=[0 for _ in range(n+1)]
for i in range(1,n+1):
in_temp=list(map(int,input().split()))
for j in range(k):
temp=in_temp[j]
need[i]+=temp*(1<<j)
edges=list()
for i in range(n-1):
edges.append(list(map(int,input().split())))
res=solution.foodTrans(solution,n,m,k,need,edges)
print(res)