[CSP]食材运输python3满分详细题解

[CSP]食材运输python3满分详细题解


前言

最近准备算法比赛,感觉这个食材运输收获挺多,特别来记录一下满分的题解和思路过程,希望可以帮助到需要的小伙伴~
在这里插入图片描述
原题在这里我就不贴了吧先。

一、解题思路

总的来说,这道题目是先给我一个树的结构,该树中有n个节点和n-1条边并且边上的权重表示的是此条路径花费的时间,总共k种食材,每个节点对于食材的的需求可能不同,每个节点也可以去需要多种食材。要求我们从不同的m个点同时出发,满足尽可能短的时间将所有的食材送到相应的节点处。输出花费的最短时间
刚看这个题目时候,很多人可能立刻就懵了,因为这里边的变量挺多。不同的食材数目k,不同的出发点数目m,再加上复杂的树的结构使好多人对这个题目望而却步。冷静分析之后,我们采用自顶向下的思路解题:

  1. 我们计从最多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[i1][ss]}}所以可以发现,我们只需要求出dp[1][s’],就可以通过动态规划得到最终的答案
  2. 求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]=min1in{g[i][s]}
  3. 求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)


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wumbuk

您的支持是我坚持的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值