题目大意
给你一颗树,每个点都有一个 \(w\) 值,代表在这个点修建工程需要花费 \(w\),还有一个 \(d\) 值,代表离这个点距离小于 \(d\) 的点可以作为这个点的建立工程的点。每条边都有权重,求出覆盖所有点的最小花费。
思路
首先分析,是否具有最优子结构性质,发现任意一颗子树的最小值,都满足最优子结构。考虑动态规划的做法。
这个题的难点在于可以选一个点来覆盖其他点,所以我们的一个想法必然是枚举这个被选取的点。
设 \(dp_{u,j}\) 表示以 \(u\) 为根节点的子树,依赖于 \(j\) 节点(其中 \(u\) 节点一定依赖 \(j\) 节点,其他节点可以依赖别的)的最小值,\(ans_u\) 表示覆盖这颗子树的最小值。
这两个状态之间的区别和联系是什么?其实 \(dp_{u,j}\) 是为了维护当前子树与它的父亲及以上节点的关系,而 \(ans_u\) 则是我们的普通动态规划设计的状态。
所以这题的关键在于将 \(dp\) 向 \(ans\) 转化。
\(dp_{u,j}=\sum_{\text{edge}(u\rightarrow v)}(dp_{v,j}-w_j,ans_v)\),意思是,u点已经确定要选j点了,下面考虑它的子节点是否也选 \(j\) 点就行。
最后用 \(dp_{u,j}\) 来更新 \(ans_u\) 即可。
总结:这题和以前做的树形 DP 有很大不同,以前做的题目都是一次设计出状态方程就行,但这个因为它同时和子节点,父节点,以及和它相距不超过 \(d\) 的节点都有关系,所以,需要先设出 \(dp_{u,j}\) 来解决相距不超过 \(d\) 的问题,再以此来解决 \(ans_u\) 的问题。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
const int N=1e4+5;
struct edge {
int to,nxt,w;
} e[N<<1];
int cnt=0,head[N],dis[N];
void add(int u,int v,int w) {
e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt,e[cnt].w=w;
}
void dfs1(int u,int fa) {
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].w;
if(v==fa) {
continue;
}
dis[v]=dis[u]+e[i].w,dfs1(v,u);
}
}
int n,best[N],f[N][N],d[N],w[N];
void dfs2(int u,int fa) {
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].w;
if(v==fa) {
continue;
}
dfs2(v,u);
}
dis[u]=0,dfs1(u,0),best[u]=1e9;
for(int i=1; i<=n; i++) {
f[u][i]=1e9;
}
for(int i=1; i<=n; i++) {
if(dis[i]<=d[u]) {
f[u][i]=w[i];
for(int j=head[u]; j; j=e[j].nxt) {
int v=e[j].to;
if(v==fa) {
continue;
}
f[u][i]+=std::min(best[v],f[v][i]-w[i]);
}
best[u]=std::min(best[u],f[u][i]);
}
}
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",w+i);
}
for(int i=1; i<=n; i++) {
scanf("%d",d+i);
}
cnt=0,memset(head,0,sizeof(head));
for(int i=1,x,y,z; i<n; i++) {
scanf("%d %d %d",&x,&y,&z),add(x,y,z),add(y,x,z);
}
dfs2(1,0),printf("%d\n",best[1]);
}
}