Task
一棵n个节点的树,给定K个点。
求从每个节点(1到n)出发,确定一个遍历顺序,求遍历完K个点不返回原点的最小距离。
N<=5e5,K<=n,边权<=1e6
Solution
普遍化问题:
① 给定一棵树,从根出发,要求遍历每个叶子节点,最后回到根,求最小距离?
距离由每条边贡献,用反证法证明每条边至少遍历2次(一上一下)。
最小距离=2*边权总和。
② 给定一棵树,从根出发,要求遍历每个叶子节点,不用回到根,求最小距离?
最后一个点x不用回到根,那么x到根路径上所有的边,都至少遍历一遍(只下不上)。
最小距离=2*边权总和-dis[x].
x为K点中根距最大的点。
回到这个问题。
如果从x出发,以x为根建树,那么K点等价于该子树的叶子节点,可以列出x值的表达式。
若对于每个x,dfs找解会超时。是否可以找到所有询问中的共性呢?
对x进行分类:在K子树上,不在K子树上。
如果不在K子树上,权值总和=K子树的权值和*2+链接K子树的边权*2。
如果在K子树上,链接值=0
因此所有的答案都是以K子树的权值为基础,再找到链接值。
列出表达式:Ans[x]=vk*2+链接值*2-dis[x]
Vk表示K子树边权和,链接值表示x到祖先上第一个属于K子树的点的距离,dis[x]表示到K子树所有点中最大距离。
列出了表达值后,只要维护关键信息就好了。
X到K子树节点中的最大值,符合“树的直径”的性质,该节点必定是K树的两个端点之一。
当然,最好自己画个图模拟一下。
const int M=5e5+3;
int head[M],ID[2];
ll dis[M];
bool vis[M];
int n,m,ecnt,rt,num;
ll vtot,dmx;//权值和,直径
struct edge{
int t,v,nxt;
}e[M<<1];
inline void addedge(int f,int t,int v){
e[++ecnt]=(edge){t,v,head[f]};
head[f]=ecnt;
}
inline void input(){
int i,j,k,a,b,c;
rd(n);rd(m);
rep(i,1,n-1){
rd(a);rd(b);rd(c);
addedge(a,b,c);
addedge(b,a,c);
}
rep(i,1,m){
rd(rt);
vis[rt]=1;//标记是K树上的点
}
}
inline void build(int f,int x){//建K树,得权值和
tral(i,x){
if(e[i].t==f)continue;
build(x,e[i].t);
if(vis[e[i].t]){
vis[x]=1;
vtot+=e[i].v*2;
}
}
}
inline void find_D(int f,int x,ll d){
if(vis[x]&&d>dmx){
ID[num]=x;
dmx=d;
}
dis[x]=max(dis[x],d);
tral(i,x){
if(e[i].t==f)continue;
find_D(x,e[i].t,d+e[i].v);
}
}
inline void find_in(int f,int x,int d){//到K子树的入口
if(vis[x])d=0;
dis[x]=2*d-dis[x];
tral(i,x){
if(e[i].t==f)continue;
find_in(x,e[i].t,d+e[i].v);
}
}
inline void init(){
build(0,rt);
find_D(0,rt,0);
dmx=0;num++;
find_D(0,ID[0],0);
find_in(0,ID[1],0);
}
inline void print(){
rep(i,1,n)sc(vtot+dis[i]);
}
int main(){
input();
init();
print();
return 0;
}