题目链接:G-火山哥周游世界
题意:一颗n个点的无根带权树,有k个特殊点,可以走到任一个点停止,现在要让你求对于每个点i作为起始点,走遍所有特殊点所花的最小路径和,
n
<
=
5
e
5
n<=5e5
n<=5e5
先考虑若要从起点出发,最后回到起点所需要的路径和,这个是到所有特殊点的所有边权加起来乘2,去和回。
若不回到起点选择某个特殊点作为终点停止,则是上述路径和减去从起点到该特殊点的路径和,故要让结果最小,必然是减去一个距离起点最远的特殊点的路径,用sum表示经过的所有边权和,len[x]表示从起点到特殊点的距离,那么在确定了起点的情况下可以得到一个这样的式子: s u m ∗ 2 − m a x ( x ∈ [ 特 殊 点 ] , l e n [ x ] ) sum*2-max(x \in [特殊点] ,len[x]) sum∗2−max(x∈[特殊点],len[x])。
也就是说只要算出来每个点作为根时的sum与那个max则可以解决该题了。
于是考虑二次扫描,第一次先算出1作为根时的答案,一次dfs即可算出。
第二次dfs时算每个点的sum与max。
- 先看sum,对于一个点x,有两部分组成,一个是x向下延展,另一个是通过父亲节点走向其他特殊点,此时只需要判断是否需要向下(下方有特殊点),以及是否需要向上即可。
- 再看max,max也是来自两部分,向下的和通过父亲节点出去的,这里需要在第一次dfs时维护路径最大值与次大值,道理在这里:树的中心。
做完了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+7;
struct Edge{
int v,w,next;
}edge[maxn<<1];
int head[maxn],top;
void init(){
top=0;
memset(head,-1,sizeof(head));
}
void add(int u,int v,int w){
edge[top].v=v;
edge[top].w=w;
edge[top].next=head[u];
head[u]=top++;
}
ll siz[maxn]; //以1为根,x的子树中国家数量;
ll sum[maxn]; //以1为根,x的子树中到国家并返回的路径和;
ll res[maxn]; //以x为根,到所有国家并返回的路径和;
ll Fmax[maxn]; //以1为根,到子树的最大值;
ll Smax[maxn]; //以1为根,到子树的次大值;
ll neber[maxn]; //以1为根时,最大值那个节点;
ll Up[maxn]; //以1为根,x通过父亲走到国家的最大值;
bool f[maxn]; // 国家;
int n,k;
void dfs(int u,int fa){
if(f[u]) siz[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
sum[u]+=sum[v];
if(siz[v]){
sum[u]+=2*w;
if(Fmax[v]+w>=Fmax[u]){
Smax[u]=Fmax[u];
Fmax[u]=Fmax[v]+w;
neber[u]=v;
}
else{
Smax[u]=max(Smax[u],Fmax[v]+w);
}
}
}
}
ll ans[maxn];
void dfs2(int u,int fa){
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(v==fa) continue;
if(k-siz[v]){
if(neber[u]==v) Up[v]=max(Up[u],Smax[u])+w;
else Up[v]=max(Up[u],Fmax[u])+w;
}
res[v]=res[u]-sum[v];
if(siz[v]) res[v]-=w*2;
res[v]+=sum[v];
if(k-siz[v]) res[v]+=2*w;
//res[v]=res[u]-sum[v]-siz[v]*w+(k-siz[v])*w+sum[v];
dfs2(v,u);
}
}
int main(){
int u,v,w;
scanf("%d%d",&n,&k);
init();
for(int i=1;i<n;++i){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
for(int i=1;i<=k;++i) scanf("%d",&u),f[u]=1;
dfs(1,0);
//for(int i=1;i<=n;++i) cout<<Fmax[i]<<" "<<Smax[i]<<" "<<neber[i]<<" "<<sum[i]<<endl;
res[1]=sum[1];
dfs2(1,0);
//for(int i=1;i<=n;++i) cout<<res[i]<<endl;
for(int i=1;i<=n;++i) ans[i]=res[i]-max(Up[i],Fmax[i]);
for(int i=1;i<=n;++i) printf("%lld\n",ans[i]);
return 0;
}