#2485. 聚会(kamp)

题目描述

一颗树 n n n 个点, n − 1 n-1 n1 条边,经过每条边都要花费一定的时间,任意两个点都是联通的。

K K K 个人(分布在 K K K 个不同的点)要集中到一个点举行聚会。

聚会结束后需要一辆车从举行聚会的这点出发,把这 K K K 个人分别送回去。

请你回答,对于 i = 1 ⋯ n i=1 \cdots n i=1n,如果在第 i i i 个点举行聚会,司机最少需要多少时间把 K K K 个人都送回家

数据范围

K ≤ N ≤ 500000 , 1 ≤ x , y ≤ N , 1 ≤ z ≤ 1000000 K \le N \le 500000,1 \le x,y \le N, 1 \le z \le 1000000 KN500000,1x,yN,1z1000000

题解

若强制走回去的话,可以看成是经过路径的两倍,所以答案是经过路径的两倍减去最长链长度

于是考虑 d p dp dp ,设 f i f_i fi 表示第 i i i 个点出发,走完其子树并且回去的路程, g i g_i gi表示 i i i 子树内,关键点离得最远的长度,可以暴力对每个点为根进行 d f s dfs dfs ,最后答案为 f r o o t − g r o o t f_{root}-g_{root} frootgroot

可以列出dp式子:
f i = ∑ f v + 2 × w i , v f_i=\sum f_v+2 \times w_{i,v} fi=fv+2×wi,v
g i = m a x { g v + w i , v } g_i=max\{g_v+w_{i,v}\} gi=max{gv+wi,v}

考虑正解,只需要 d f s dfs dfs 一次,然后进行换根操作即可

效率: O ( n ) O(n) O(n)

代码

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=5e5+5,M=N<<1;
int n,k,t,sz[N],fl[N],hd[N],V[M],W[M],nx[M];
LL f[N],g[N],ans[N];
void add(int u,int v,int w){
	V[++t]=v;nx[t]=hd[u];hd[u]=t;W[t]=w;
}
void dfs(int x,int fr){
	sz[x]=fl[x];f[x]=g[x]=0;
	for (int i=hd[x];i;i=nx[i])
		if (V[i]!=fr){
			dfs(V[i],x);sz[x]+=sz[V[i]];
			if (sz[V[i]])
				f[x]+=f[V[i]]+2ll*W[i],
				g[x]=max(g[x],g[V[i]]+W[i]);
		}
}
void dp(int x,int fr,int w){
	ans[x]=f[x]-g[x];
	LL ax1=0,ax2=0;int id=0;
	for (int i=hd[x];i;i=nx[i])
		if (V[i]!=fr && sz[V[i]]){
			if (ax1<g[V[i]]+W[i])
				ax2=ax1,ax1=g[V[i]]+W[i],id=V[i];
			else ax2=max(ax2,g[V[i]]+W[i]);
		}
	if (sz[fr]){
		if (ax1<g[fr]+w)
			ax2=ax1,ax1=g[fr]+w,id=fr;
		else ax2=max(ax2,g[fr]+w);
	}
	for (int i=hd[x];i;i=nx[i])
		if (V[i]!=fr){
			if (sz[V[i]]){
				f[x]-=f[V[i]]+2ll*W[i];
				if (id==V[i]) g[x]=ax2;
				f[V[i]]+=f[x]+2ll*W[i];
				g[V[i]]=max(g[V[i]],g[x]+W[i]);
			}
			else f[V[i]]=f[x]+2ll*W[i],g[V[i]]=g[x]+W[i];
			sz[x]-=sz[V[i]];sz[V[i]]+=sz[x];dp(V[i],x,W[i]);
			sz[V[i]]-=sz[x];sz[x]+=sz[V[i]];
			if (sz[V[i]]){
				f[V[i]]-=f[x]+2ll*W[i];
				f[x]+=f[V[i]]+2ll*W[i];g[x]=ax1;
			}
		}
}
int main(){
	scanf("%d%d",&n,&k);
	for (int x,y,z,i=1;i<n;i++)
		scanf("%d%d%d",&x,&y,&z),
		add(x,y,z),add(y,x,z);
	for (int x,i=1;i<=k;i++)
		scanf("%d",&x),fl[x]=1;
	dfs(1,0),dp(1,0,0);
	for (int i=1;i<=n;i++)
		printf("%lld\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值