G 火山哥周游世界(树上走过确切k个点的最短时间,树形dp)
题:https://ac.nowcoder.com/acm/contest/4114/G
题意:给定一棵树,给定m个确定的点,问从1~n为起点开始,走过着m个点的最短距离是多少(不用回来)
分析:考虑根为1的情况,那么答案就是(所有1到m个点的边距离和)*2-(1到最远点的距离),因为不用回来;
考虑换根,假设节点 i 到其子树的确定点的距离和的2倍为sum[i],在第一次dfs过程中我们可以算出每一个的sum[i],然后将sum[i]转义成以 i 为根到m个点的边的距离和的2倍;
关键是考虑换根后sum[i]的父亲边的更新:先令:sum[i]=sum[fa[i]] 若 i 子树中有确定点,那么-=2*w,若父亲边有确定点,那么+=2*w;
重要的一步是 i 到最远点距离的更新,假设更新fa->i , T节点为fa为根的最远节点;
· 要是fa->i 不是fa->T的路径,那么 i 的最远距离即为fa的最远距离+w(i与fa的边权)
·否则,说明fa的最远距离给占用,那么肯定是w+x,根据贪心,这个x必须为当fa为根节点时离fa次远的距离;
所以就需要维护最远距离fir和次远距离sec。
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pb push_back #define MP make_pair #define pil pair<int,ll> const int inf=0x3f3f3f3f; const ll INF=1e18; const int M=5e5+5; vector<pil >g[M]; int n,m,sz[M],nex[M]; ll fir[M],sec[M],sum[M]; void upd(ll x,int u,int v){ if(x>=fir[u]){ sec[u]=fir[u],fir[u]=x; nex[u]=v; } else sec[u]=max(sec[u],x); } void dfs1(int u,int fa){ for(auto it:g[u]){ int v=it.first; ll w=it.second; if(v!=fa){ dfs1(v,u); sz[u]+=sz[v]; if(sz[v]){ sum[u]+=sum[v]+2ll*w; upd(fir[v]+w*(sz[v]!=0),u,v); } } } } void dfs2(int u,int fa){ for(auto it:g[u]){ int v=it.first; ll w=it.second; if(v!=fa){ sum[v]=sum[u]-2ll*w*(sz[v]!=0)+2ll*w*(m-sz[v]!=0); if(sz[v]!=m){ if(v==nex[u]) upd(sec[u]+w,v,0); else upd(fir[u]+w,v,0); } dfs2(v,u); } } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<n;i++){ int u,v; ll w; scanf("%d%d%lld",&u,&v,&w); g[u].pb(MP(v,w)); g[v].pb(MP(u,w)); } for(int x,i=1;i<=m;i++){ scanf("%d",&x); sz[x]=1; } dfs1(1,0); dfs2(1,0); for(int i=1;i<=n;i++) printf("%lld\n",sum[i]-fir[i]); return 0; }