有向树上k中值问题

问题描述

给一棵以 0 0 0 为根节点的有向树 T T T(树上的边都由儿子指向父亲),每个节点 u u u 都有权值 w ( u ) w(u) w(u),每条边 ( u , v ) (u,v) (u,v) 都有非负边长 d ( u , v ) d(u,v) d(u,v)

节点的权值代表节点需求的服务量,边长可以看作运输费用。树上的任何节点可以设置服务机构以满足服务需求。如果节点 u u u 有服务机构,则满足该节点的服务需求可以不消耗运输费用,否则要将服务沿着树的边转移到某个有服务机构的节点 v v v 中,需要 ω ( u ) ⋅ d ( u , v ) \omega(u) \cdot d(u,v) ω(u)d(u,v) 的运输费用 d ( u , v ) d(u,v) d(u,v) 代表 u u u v v v 的最短距离)。

有向树的根节点已经设置了一个服务中心,现在再增设 k k k 个服务机构,使得树上每个节点的服务需求都能得到满足的运输费用最小,求出这个最小值。

输入格式

1 1 1 行有 2 2 2 个正整数 n n n k k k n n n 表示有向树 T T T 的边数; k k k 是要增设的服务机构数。有向树 T T T 的顶点编号为 0 , 1 , 2 , … , n 0,1,2,\dots,n 0,1,2,,n。根结点编号为 0 0 0。接下来的 n n n 行中,每行有表示有向树 T T T 的一条有向边的 3 3 3 个整数。第 i + 1 i+1 i+1 行的 3 3 3 个整数 w i , v i , d i w_i,v_i,d_i wi,vi,di 分别表示编号为 i i i 的顶点的权为 w i w_i wi,相应的有向边为 ( i , v i ) (i,v_i) (i,vi) ,其边长为 d i d_i di

输出格式

输出一个整数代表最小运输费用。

输入样例
4 2
1 0 1
1 1 10
10 2 5
1 2 3
输出样例
4

问题分析

  • 由于服务转移只能沿着边转移,因此节点只能将服务转移到自己的祖先节点上。

  • 同理祖先节点也只能影响自己的子孙节点。

  • 容易发现每个子树的子问题都相对独立、考虑树形DP

  • 对于一颗根节点为 v v v 的子树,如果确定了离 v v v 最近的服务中心节点 u u u 以及 v v v 中能增设的服务中心数量 k v k_v kv ;就可以唯一确定该子树的最小运输费用 d p [ v ] [ u ] [ k v ] dp[v][u][k_v] dp[v][u][kv] ;这个状态可以实现转移而不产生后效性。

  • 此时答案储存在 d p [ 0 ] [ 0 ] [ k ] dp[0][0][k] dp[0][0][k]

问题解答

  • 对于一个有多个儿子的节点 v v v 要将其状态转移到儿子上需要将 k v k_v kv 划分为多份进行考虑,过于复杂 。

  • 在一个节点上设置服务中心后只会影响这个节点的子孙;不会影响兄弟节点,因此可以考虑将树转化为二叉树后再进行考虑DP。

  • 先预处理初始的树上任意两点 v 、 u v、u vu 之间的距离 d [ v ] [ u ] d[v][u] d[v][u] ,然后在二叉树上对于每个节点开始转移。

  • 对于二叉树节点 v v v 若其不增设服务中心,则 v v v 中服务必须转移到 v v v ;距儿子节点的最近服务中心不需更新;直接枚举分配给左右儿子的服务机构数量。

    可得最小费用 w l s = min ⁡ i = 0 k v { d p [ v . l s ] [ u ] [ i ] + d p [ v . r s ] [ u ] [ k v − i ] } + d [ v ] [ u ] ⋅ w [ v ] wls=\mathop{\min}_{i=0}^{k_v} \{ dp[v.ls][u][i]+dp[v.rs][u][k_v-i] \}+d[v][u] \cdot w[v] wls=mini=0kv{dp[v.ls][u][i]+dp[v.rs][u][kvi]}+d[v][u]w[v]

  • 对于二叉树节点 v v v 若其增设服务中心,则 v v v 中服务不产生费用;距左儿子节点最近服务中心需更新,右儿子不更新;再枚举分配给左右儿子的服务机构数量。

    可得最小费用 w r s = min ⁡ i = 0 k v − 1 { d p [ v . l s ] [ v ] [ i ] + d p [ v . r s ] [ u ] [ k v − 1 − i ] } wrs=\mathop{\min}_{i=0}^{k_v-1} \{ dp[v.ls][v][i]+dp[v.rs][u][k_v-1-i] \} wrs=mini=0kv1{dp[v.ls][v][i]+dp[v.rs][u][kv1i]}

  • 状态转移方程 :

    d p [ v ] [ u ] [ k v ] = min ⁡ ( w l s , w r s ) dp[v][u][k_v]= \min(wls,wrs) dp[v][u][kv]=min(wls,wrs)

  • 由于转移方向不确定,因此采用记忆化搜索;对于二叉树中节点 v v v ,若 v v v 的子孙数量大于 k v k_v kv 则答案必定为 0 0 0,可以剪枝。

  • 以下为 P y t h o n Python Python 程序实现

    a=input().split()
    n=int(a[0])
    kk=int(a[1])
    
    au=[]
    for i in range(n+2):
      au.append(range(i))
    
    w=[0]*(n+1)
    sons=[0]*(n+1)
    tree=[[-1]*2 for i in au[n+1]] #储存二叉树 tree[i][0]代表i左节点;tree[i][1]代表i右节点
    fa=[0]*(n+1)
    d=[[1e9]*(n+1) for i in au[n+1]]
    
    for i in range(n): #读入并将树转化为二叉树
      a=input().split()
      w[i+1]=int(a[0])
      v=int(a[1])
      fa[i+1]=v
      di=int(a[2])
      d[i+1][v]=di  
      if (tree[v][0]==-1):
        tree[v][0]=i+1
      else:
        tree[i+1][1]=tree[v][0]
        tree[v][0]=i+1
        
    for i in au[n+1]: #预处理原树上任意两点间距离
      pl=i
      cc=0
      while (pl!=0):
        d[i][pl]=cc
        cc=cc+d[pl][fa[pl]]
        pl=fa[pl]
      d[i][pl]=cc
    
    def so(pl): #求二叉树子树节点个数 
      ans=1
      if (tree[pl][0]!=-1): 
        ans=ans+so(tree[pl][0])
      if (tree[pl][1]!=-1): 
        ans=ans+so(tree[pl][1])
      sons[pl]=ans
      return sons[pl];
    
    dp=[[[1e9]*(n+1) for i in au[n+1]]for i in au[n+1]]
    def _dp(v,u,kv):
      if (dp[v][u][kv]<1e9): 
        return dp[v][u][kv]
      if (kv>=sons[v]):  #剪枝
        dp[v][u][kv]=0
        return 0
        
      for i in au[kv+1]:  #假设节点v不设置服务中心 
        ans=d[v][u]*w[v]
        if (tree[v][0]!=-1):
          ans=ans+_dp(tree[v][0],u,i)
        if (ans>dp[v][u][kv]): #剪枝
          continue
        if (tree[v][1]!=-1):
          ans=ans+_dp(tree[v][1],u,kv-i)
        dp[v][u][kv]=min(dp[v][u][kv],ans)
      
      if(v==0):
        return dp[u][v][kv]
        
      for i in au[kv]: #假设节点设置了服务中心
        ans=0
        if (tree[v][0]!=-1):
          ans=ans+_dp(tree[v][0],v,i)
        if (ans>dp[v][u][kv]):   #剪枝 
          continue
        if (tree[v][1]!=-1):
          ans=ans+_dp(tree[v][1],u,kv-1-i)
        dp[v][u][kv]=min(dp[v][u][kv],ans)
      return dp[v][u][kv]
      
    so(0)
    print(_dp(0,0,kk))
    
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值