树形DP/换根DP 习题
Part 1:树形DP
选边
题意
一棵树有 \(n\) 个结点,\(n-1\) 条边,第 \(i\) 条边是:\(u[i],v[i],w[i]\) 表示结点 \(u[i]\) 与结点 \(v[i]\) 有一条权值为 \(w[i]\) 的无向边。
你需要从这 \(n-1\) 条边当中选取若干条边(可以不选),使得被选中的边的权值权值总和最大,但是有一些限制:
对于第 \(i\) 个点来说,与点 \(i\) 相连的所有边当中最多只能有 \(d[i]\) 条边被选中
解题思路
-
设 \(f[x][0]\) 表示 \(x\) 子树最大的选边权值和,且 \(x\) 不与它的父亲连边;\(f[x][1]\) 表示 \(x\) 子树最大的选边权值和,且 \(x\) 与它的父亲连边
-
设 \(x\) 的儿子为 \(y\),对于任意的 \(x\),我们可以想让 \(f[x][0]\) 和 \(f[x][1]\) 加上 \(f[y][0]\),表示 \(y\) 不与 \(x\) 连边,再来考虑 \(y\) 与 \(x\) 连边的情况
-
对于任意的 \(y\),若 \(f[y][1]>f[y][0]\),则可以考虑连边。我们可以将 \(f[y][1]-f[y][0]\) 压入一个 \(vector\),并从大到小排序。\(f[x][1]\) 取前 \(d[x]-1\) 个,\(f[x][0]\) 取前 \(d[x]\) 个即可
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=300010;
int n,d[N];
LL f[N][2];
int head[N],edge[2*N],nxt[2*N],ver[2*N],tot;
void add(int x,int y,int z)
{
ver[++tot]=y; edge[tot]=z;
nxt[tot]=head[x]; head[x]=tot;
}
void dp(int x,int fa)
{
vector <LL> a;
for(int i=head[x]; i; i=nxt[i])
{
int y=ver[i],z=edge[i];
if(y==fa)
continue;
dp(y,x);
f[x][0]+=f[y][1];
f[x][1]+=f[y][1];
if(d[y] && (LL)z+f[y][0]>f[y][1])
a.push_back((LL)z+f[y][0]-f[y][1]);
}
sort(a.begin(),a.end(),greater<int>());
int len=a.size();
for(int i=0; i<min(d[x],len); i++)
f[x][1]+=a[i];
for(int i=0; i<min(d[x]-1,len); i++)
f[x][0]+=a[i];
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&d[i]);
for(int i=1; i<n; i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dp(1,0);
printf("%lld",f[1][1]);
return 0;
}
交通违法
题意
禅城区有 \(N\) 条双向道路和 \(N\) 个路口,路口的编号从 \(1\) 至 \(N\)。每条道路连接两个路口。
两个路口之间不会有重边。保证任意两个路口都是相互到达的。
现在觉得在一些路口装上摄像头,检测路面的违法情况。
装在路口的摄像头可以监测到所有连接到这个路口的道路。
现在的问题是:最少需要在多少个路口安装摄像头才能监测所有的道路?
解题思路
-
容易看出这是一个基环树,对于环上两个相邻的节点 \(x\) 和 \(y\),至少要有一个节点安装了摄像头
-
所以我们可以用一次 DFS 找出 \(x\) 和 \(y\),并分别对以 \(x\) 和 \(y\) 为根时的情况进行树形DP,两者答案取最小值即可
-
DP过程较简单,可自行思考
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,f[N][2],a,b,ans;
bool v[N],flag;
vector <int> g[N];
void dfs(int x,int fa)
{
if(flag)
return;
v[x]=1;
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
if(v[y])
{
a=x,b=y;
flag=1;
return;
}
dfs(y,x);
if(flag)
return;
}
}
void dp(int x,int fa)
{
f[x][1]=1;
f[x][0]=0;
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
dp(y,x);
f[x][1]+=min(f[y][1],f[y][0]);
f[x][0]+=f[y][1];
}
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
int tot,son;
scanf("%d",&tot);
for(int j=1; j<=tot; j++)
{
scanf("%d",&son);
g[i].push_back(son);
}
}
dfs(1,0);
g[a].erase(remove(g[a].begin(),g[a].end(),b),g[a].end());
g[b].erase(remove(g[b].begin(),g[b].end(),a),g[b].end());
dp(a,0);
ans=f[a][1];
dp(b,0);
ans=min(ans,f[b][1]);
printf("%d",ans);
return 0;
}
Part 2:树形背包
修复道路
题意
有一颗树,\(N\) 个结点,那么至少要删除多少条边之后,使得存在一颗子树,该子树恰好有 \(P\) 个结点?
解题思路
-
设 \(f[x][j]\) 表示 \(x\) 子树有 \(j\) 个节点至少要删几条边。对于节点 \(x\) 和 它的儿子 \(y\) 来说,有两种情况:
- 如果删掉 \(x\) 与 \(y\) 之间的边,则所有的 \(f[x][j]++\)
- 如果不删掉,则对 \(f[x]\) 做树形背包。容易得出状态转移方程为\[f[x][j+k]=\min\{f[x][j]+f[y][k]\}\:(j\leq size[x],k\leq size[y]) \]
-
统计 \(ans\) 时,先让 \(ans=f[1][p]\),之后再让 \(ans=\min(ans,f[2 \dots n][p])\)
代码
#include<bits/stdc++.h>
using namespace std;
const int N=160,M=110;
int n,p,f[N][N],size[N],tmp[N];
vector <int> g[N];
void dp(int x,int fa)
{
f[x][1]=0;
size[x]=1;
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
dp(y,x);
for(int j=1; j<=size[x]; j++)
tmp[j]=f[x][j];
for(int j=1; j<=size[x]; j++)
f[x][j]++;
for(int j=1; j<=size[x]; j++)
for(int k=1; k<=size[y]; k++)
f[x][j+k]=min(f[x][j+k],tmp[j]+f[y][k]);
size[x]+=size[y];
}
}
int main()
{
scanf("%d%d",&n,&p);
for(int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
memset(f,0x3f,sizeof(f));
dp(1,0);
int ans=f[1][p];
for(int i=2; i<=n; i++)
ans=min(ans,f[i][p]+1);
printf("%d",ans);
return 0;
}
无向图染色
题意
有一个 \(n\) 个结点 \(m\) 条边的无向图,不存在环。
一开始所有的结点都是白色的。
如果想把第 \(i\) 个结点染成黑色,需要 \(cost[i]\) 元。
对于第 \(i\) 个结点来说,与它直接有边相连的结点被称为结点 \(i\) 的朋友。
对于第 \(i\) 个结点来说,如果把第 \(i\) 个结点染成黑色,而且它的朋友当中,至少也有一个结点是黑色,那么第 \(i\) 个结点就是“黑色开心”结点。
现在有 \(Q\) 个问题,第 \(i\) 个问题给出一个整数 \(a[i]\),你需要回答:
假设给你 \(a[i]\) 元,你应该如何染色,使得“黑色开心”结点的数量最多?输出最多的黑色开心”结点的数量。
解题思路
-
注意到整个图并不联通,所以需要对每个连通块进行操作
-
设 \(dp[j]\) 表示有 \(j\) 个开心节点的最小花费,容易想到需要对每个连通块进行操作再合并
-
对每个连通块使用树形DP,设 \(f[x][j][0/1]\) 表示 \(x\) 子树有 \(j\) 个开心节点且 \(x\) 是/不是黑色开心节点的最小话费
-
设 \(y\) 是 \(x\) 的一个儿子,\(k \in [0,size[y]]\),\(tmp=\min(f[y][k][0],f[y][k][1])\)
-
下面考虑 \(f[x][j][0]\) 的转移
- 因为 \(x\) 不是黑色开心节点,所以容易得出 \(f[x][j][0]=\min(f[x][j][0],f[x][j-k][0]+tmp)\)
-
下面考虑 \(f[x][j][1]\) 的转移
-
若 \(x\) 本身就是黑色开心节点,则 \(f[x][j][1]=\min(f[x][j][1],f[x][j-k][1]+tmp)\)
-
若 \(x\) 原先是白色,则现在需要染成黑色,且它有一个儿子是黑色,那么有两种情况:
-
\(y\) 是黑色开心节点,则 \(f[x][j][1]=\min(f[x][j][1],f[x][j-k-1][0]+cost[x]+f[y][k][1])\)
-
\(y\) 是白色节点,现在需要染成黑色,则 \(f[x][j][1]=\min(f[x][j][1],f[x][j-k-2][0]+cost[x]+cost[y]+f[y][k][0])\)
-
-
若 \(x\) 是黑色开心节点,但 \(y\) 是白色,则可以考虑将 \(y\) 染成黑色,\(f[x][j][1]=\min(f[x][j][1],f[x][j-k-1][1]+cost[y]+f[y][k][0])\)
-
-
之后,对于每个连通块进行合并。设 \(i\) 是当前连通块的根,\(k\in [0,size[i]]\),则状态转移方程为 \(dp[j]=\min(dp[j],dp[j-k]+min(f[i][k][0],f[i][k][1]));\)
-
但是注意到,\(dp[j]\) 不连续且没有单调性,所以需要将 \(dp[j]\) 从小到大排序,再将询问离线,也是从小到大排序,最后利用单调队列求解
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010,Q=200010,M=10000000000000;
struct node
{
int w,num;
} dp[N];
struct ask
{
int id,money;
} a[Q];
int n,m,cost[N];
int q,ans[Q];
int size[N],f[N][N][2];
int head[N],ver[2*N],nxt[2*N],tot;
bool vis[N];
bool cmp1(node x,node y)
{
return x.w<y.w;
}
bool cmp2(ask x,ask y)
{
return x.money<y.money;
}
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs(int x,int fa)
{
vis[x]=1;
size[x]=1;
f[x][0][0]=0;
for(int i=head[x]; i; i=nxt[i])
{
int y=ver[i];
if(y==fa || vis[y])
continue;
dfs(y,x);
size[x]+=size[y];
for(int j=size[x]; j>=1; j--)
{
for(int k=0; k<=min(size[y],j); k++)
{
int tmp=min(f[y][k][0],f[y][k][1]);
if(f[x][j-k][0]<=M)
f[x][j][0]=min(f[x][j][0],f[x][j-k][0]+tmp);
if(f[x][j-k][1]<=M)
f[x][j][1]=min(f[x][j][1],f[x][j-k][1]+tmp);
if(j-k-1>=0 && f[x][j-k-1][0]<=M && f[y][k][1]<=M)
f[x][j][1]=min(f[x][j][1],f[x][j-k-1][0]+cost[x]+f[y][k][1]);
if(j-k-2>=0 && f[x][j-k-2][0]<=M && f[y][k][0]<=M)
f[x][j][1]=min(f[x][j][1],f[x][j-k-2][0]+cost[x]+cost[y]+f[y][k][0]);
if(j-k-1>=0 && f[x][j-k-1][1]<=M && f[y][k][0]<=M)
f[x][j][1]=min(f[x][j][1],f[x][j-k-1][1]+cost[y]+f[y][k][0]);
}
}
}
}
signed main()
{
memset(f,0x7f,sizeof(f));
memset(dp,0x7f,sizeof(dp));
scanf("%lld%lld",&n,&m);
for(int i=1; i<=n; i++)
scanf("%lld",&cost[i]);
for(int i=1; i<=m; i++)
{
int x,y;
scanf("%lld%lld",&x,&y);
add(x,y);
add(y,x);
}
dp[0].w=0;
for(int i=0; i<=n; i++)
dp[i].num=i;
int len=0;
for(int i=1; i<=n; i++)
{
if(vis[i])
continue;
dfs(i,0);
len+=size[i];
for(int j=len; j>=1; j--)
{
for(int k=0; k<=size[i]; k++)
if(j>=k && dp[j-k].w<=M && min(f[i][k][0],f[i][k][1])<=M)
dp[j].w=min(dp[j].w,dp[j-k].w+min(f[i][k][0],f[i][k][1]));
}
}
sort(dp,dp+1+n,cmp1);
scanf("%lld",&q);
for(int i=1; i<=q; i++)
{
scanf("%lld",&a[i].money);
a[i].id=i;
}
sort(a+1,a+1+q,cmp2);
int l=0,val=0;
for(int i=1; i<=q; i++)
{
while(l<=n && dp[l].w<=a[i].money)
{
val=max(val,dp[l].num);
l++;
}
ans[a[i].id]=val;
}
for(int i=1; i<=q; i++)
printf("%lld\n",ans[i]);
return 0;
}
Part 3:换根DP
Maximum White Subtree
(题目传送门)
题意
给定一棵 \(n\) 个节点无根树,每个节点 \(u\) 有一个颜色 \(a_u\),若 \(a_u\) 为 \(0\) 则 \(u\) 是黑点,若 \(a_u\) 为 \(1\) 则 \(u\) 是白点。
对于每个节点 \(u\),选出一个包含 \(u\) 的连通子图,设子图中白点个数为 \(cnt_1\),黑点个数为 \(cnt_2\),请最大化 \(cnt_1 - cnt_2\)。并输出这个值。
解题思路
一、DFS1
-
设 \(a[i]\) 表示 \(i\) 的子树对 \(i\) 的贡献
-
则对于 \(x\) 的儿子 \(y\),\(a[x]+=a[y]\)
二、DFS2
-
设 \(f[x]\) 表示以 \(x\) 为根时的答案,则对于 \(x\) 的孩子 \(y\)
-
若 \(a[y]>0\),则它对 \(f[x]\) 有贡献,所以 \(f[y]=\max(a[y],f[x])\)
-
若 \(a[y]<0\),则它对 \(f[x]\) 无贡献,所以 \(f[y]=\max(a[y],a[y]+f[x])\)
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=200010;
int n,a[N],f[N];
vector <int> g[N];
void dfs1(int x,int fa)
{
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
dfs1(y,x);
if(a[y]>0)
a[x]+=a[y];
}
}
void dfs2(int x,int fa)
{
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
if(a[y]>0)
f[y]=max(a[y],f[x]);
else
f[y]=max(a[y],a[y]+f[x]);
dfs2(y,x);
}
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
if(a[i]==0)
a[i]=-1;
}
for(int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1,0);
f[1]=a[1];
dfs2(1,0);
for(int i=1; i<=n; i++)
printf("%d ",f[i]);
return 0;
}
蚂蚁聚会
题意
有 \(n\) 个蚁巢,这 \(n\) 个蚁巢形成一颗树形结构,第 \(i\) 个蚁巢有 \(a[i]\) 只蚂蚁。现在蚂蚁们想举行一个大型的聚会。
但是这些蚂蚁比较懒惰,都不想走太远,每只蚂蚁最多只愿意走 \(t\) 步(每一步就是走一条边)。
它们要计算:如果选择第 \(i\) 个蚁巢作为举行聚会的地点,可以有多少只蚂蚁参加聚会?记该数量为 \(p[i]\)。
你的任务就是帮助计算: \(p[1],p[2],p[3],\dots p[n]\)。
解题思路
考虑换根DP
-
设 \(f[x][i]\) 表示 \(x\) 的子树中距离 \(x\) 为 \(i\) 的蚂蚁总数量
-
设 \(g[x][i]\) 表示以 \(x\) 为根,距离 \(x\) 为 \(i\) 的蚂蚁总数量
一、DFS1
- 先以 \(1\) 为根,用一次 DFS 求出 \(f[1\dots n][1\dots t]\)
- 初始化:\(f[x][0]=a[x]\)
二、DFS2
-
易得 \(g[1][1\dots t]=f[1][1\dots t]\)
-
设 \(y\) 是 \(x\) 的儿子,那么下面考虑如何由 \(g[x][1\dots t]\) 推出 \(g[y][1\dots t]\)
-
\(g[y][i]\) 由两部分组成,第一部分是在 \(y\) 子树内走 \(i\) 步上去,为 \(f[y][i]\)
-
第二部分是走 \(i-1\) 步到 \(x\) 节点再往下走 \(1\) 步到 \(y\),为 \(g[x][i-1]-f[y][i-2]\)
-
所以 \(g[y][i]=f[y][i]+g[x][i-1]-f[y][i-2]\)
-
注意下标不要越界
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010,X=25;
int n,t,a[N],f[N][X],ans[N][X];//ans数组就是g数组
vector <int> g[N];
void dfs1(int x,int fa)
{
f[x][0]=a[x];
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
dfs1(y,x);
for(int j=1; j<=t; j++)
f[x][j]+=f[y][j-1];
}
}
void dfs2(int x,int fa)
{
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
for(int j=0; j<=t; j++)
{
if(j>=2)
ans[y][j]=f[y][j]+ans[x][j-1]-f[y][j-2];
else if(j>=1)
ans[y][j]=f[y][j]+ans[x][j-1];
else
ans[y][j]=f[y][j];
}
dfs2(y,x);
}
}
int main()
{
scanf("%d%d",&n,&t);
for(int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
dfs1(1,0);
for(int i=0; i<=t; i++)
ans[1][i]=f[1][i];
dfs2(1,0);
for(int i=1; i<=n; i++)
{
int sum=0;
for(int j=0; j<=t; j++)
sum+=ans[i][j];
printf("%d\n",sum);
}
return 0;
}
Kamp
(题目传送门)
题意
一颗树 \(n\) 个点,\(n-1\) 条边,经过每条边都要花费一定的时间,任意两个点都是联通的。
有 \(K\) 个人(分布在 \(K\) 个不同的点)要集中到一个点举行聚会。
聚会结束后需要一辆车从举行聚会的这点出发,把这 \(K\) 个人分别送回去。
请你回答,对于 \(i=1 \sim n\) ,如果在第 \(i\) 个点举行聚会,司机最少需要多少时间把 \(K\) 个人都送回家。
解题思路
考虑换根DP
-
设 \(size[i]\) 表示:在子树 \(i\) 内的顾客的数量
-
设 \(f[i][0/1]\) 表示:在子树 \(i\) 内,最远/第二远的顾客到达节点 \(i\) 的所需时间
说明:\(f[i][0]\) 和 \(f[i][1]\) 是来自于 \(i\) 的不同儿子子树
-
设 \(a[i]\) 表示: 司机从 \(i\) 点出发,送完 \(i\) 子树内的所有顾客,然后司机返回到节点 \(i\) ,所需时间的总和。
-
设 \(g[i][0/1]\) 表示:若整棵树以 \(i\) 为根,最远/第二远的顾客到达节点 \(i\) 的所需时间。
说明:\(g[i][0]\) 和 \(g[i][1]\) 是来自于 \(i\) 的不同儿子子树
-
设 \(b[i]\) 表示: 若整棵树以 \(i\) 为根, 司机从 \(i\) 号点出发,送完所有顾客,然后司机返回节点 \(i\),所需时间的总和。
那么第 \(i\) 个节点的答案就是 \(b[i]-g[i][0]\)。(最长时间的那个人最后送,无需返回根结点)
一、DFS1
-
先以 \(1\) 号节点为根,DFS一次,求出所有的 \(f[i][0\dots1]\)、\(size[i]\)、\(a[i]\)。
-
考虑边界,若 \(i\) 是叶子节点,\(f[i][0] = 0,\:\:f[i][1] = 0,\:\:a[i] = 0\)。
二、DFS2
-
对于 \(1\) 号节点来说,\(b[1]=a[1],\:\:g[1][0/1]=f[1][0/1]\)
-
对于 \(x\) 的儿子 \(y\) 来说,设 \(len(x,y)=z\)
-
下面讨论 \(b[y]\) 的取值情况:
-
若 \(x\) 的其它儿子节点没有顾客,即 \(size[y]=k\),此时 \(b[y]=a[y];\)
-
若 \(y\) 的儿子节点没有顾客,即 \(size[y]=0\),此时 \(b[y]=b[x]+2*z\)
-
若 \(x\) 的其他儿子和 \(y\) 的儿子都有顾客,此时 \(b[y]=b[x]\)
-
-
下面讨论 \(g[y][0/1]\) 的取值情况
-
若 \(size[y]=k\),此时 \(x\) 号节点为根的树当中,只有 \(y\) 号子树内有顾客,故 \(g[2][0] = f[2][0],\:\:g[2][1] = f[2][1]\)。
-
否则,要先从以 \(x\) 号结点为整棵树的根时,找出不是来自于 \(y\) 号子树的顾客到达 \(x\) 号结点的最长时间路径,不妨记为 \(p\)
-
若能计算出 \(p\),则 \(g[y][0]=\max(p+z,f[y][0])\),\(g[y][1]\)等于 \(\{f[2][0],\:f[2][1],\:p+z\}\) 的第二大。(因为 \(g[i][0]\) 和 \(g[i][1]\) 是来自于 \(i\) 的不同儿子子树,所以 \(g[y][1]\) 无需考虑从非 \(y\) 的 \(x\) 儿子节点取第 \(2\) 大的)
-
-
下面讨论 \(p\) 的取值
-
若 \(size[y]=0\),则易得 \(p=g[x][0]\)
-
若 \(f[y][0]+z=g[x][0]\),则 \(p=g[x][1]\)
-
否则,\(p=g[x][0]\)
-
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=500010;
struct node
{
int ver,edge;
};
int n,k,size[N];
LL f[N][2],a[N],g[N][2],b[N];
vector <node> gg[N];
void dfs1(int x,int fa)
{
for(int i=0; i<gg[x].size(); i++)
{
int y=gg[x][i].ver,z=gg[x][i].edge;
if(y==fa)
continue;
dfs1(y,x);
if(size[y]==0)
continue;
size[x]+=size[y];
a[x]+=a[y]+1LL*2*z;
if(f[y][0]+z>f[x][0])
{
f[x][1]=f[x][0];
f[x][0]=f[y][0]+z;
}
else if(f[y][0]+z>f[x][1])
f[x][1]=f[y][0]+z;
}
}
void dfs2(int x,int fa)
{
for(int i=0; i<gg[x].size(); i++)
{
int y=gg[x][i].ver,z=gg[x][i].edge;
if(y==fa)
continue;
if(size[y]==k)
b[y]=a[y];
else if(size[y]==0)
b[y]=b[x]+1LL*2*z;
else
b[y]=b[x];
if(k==size[y])
{
g[y][0]=f[y][0];
g[y][1]=f[y][1];
}
else
{
LL p=0;
if(size[y]==0)
p=g[x][0];
else if(f[y][0]+z==g[x][0])
p=g[x][1];
else p=g[x][0];
if(p+z>f[y][0])
{
g[y][0]=p+z;
g[y][1]=f[y][0];
}
else
{
g[y][0]=f[y][0];
g[y][1]=max(f[y][1],p+z);
}
}
dfs2(y,x);
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1; i<n; i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
gg[x].push_back(node{y,z});
gg[y].push_back(node{x,z});
}
for(int i=1; i<=k; i++)
{
int x;
scanf("%d",&x);
size[x]=1;
}
dfs1(1,0);
g[1][0]=f[1][0];
g[1][1]=f[1][1];
b[1]=a[1];
dfs2(1,0);
for(int i=1; i<=n; i++)
printf("%lld\n",b[i]-g[i][0]);
return 0;
}

浙公网安备 33010602011771号