问题描述
给一棵以 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 v、u 之间的距离 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][kv−i]}+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=0kv−1{dp[v.ls][v][i]+dp[v.rs][u][kv−1−i]} 。
-
状态转移方程 :
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))