DTOJ #2485. 聚会(kamp)

【题目描述】

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

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

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

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

【输入格式】

第一行两个数,$n,K$。

接下来 $n-1$ 行,每行三个数,$x,y,z$ 表示 $x$ 到 $y$ 之间有一条需要花费 $z$ 时间的边。

接下来 $K$ 行,每行一个数,表示 $K$ 个人的分布。

【输出格式】

输出 $n$ 个数,第 $i$ 行的数表示:如果在第 $i$ 个点举行聚会,司机需要的最少时间。

【样例】

样例输入
7 2
1 2 4
1 3 1
2 5 1
2 4 2
4 7 3
4 6 2
3
7

样例输出
11
15
10
13
16
15
10

【数据范围与提示】

$ K \le N \le 500000 $

$ 1 \le x,y \le N, 1 \le z \le 1000000 $

【题解】

看到给出 $k$ 个点,考虑虚树。所以这题并不需要虚树(当然如果你想建没人拦你)

题意是给定一棵树,求从任意点出发走过所有特殊点的最短路径。

先考虑回到出发点的完整回路。由于树上两点间路径只有一条,而每个特殊点都必须到达,所以所有特殊点到出发的路径都至少走了两遍。而显然每条边最多只会走两遍,因此计算所有特殊点到出发点路径的边权和的两倍即可。

考虑不回到出发点。显然可以贪心,找出最远的特殊点,将答案减掉该路径长路一倍即可。

至于换根,是经典的换根 $dp$,$dfs$ 两遍即可维护。注意第二次 $dfs$ 从上往下更新 $dp$ 值时要记录最长链和次长链,保证不会重复。

【代码】

#include<bits/stdc++.h>
inline int read ( void )
{
	int x=0;char ch;bool f=true;
	while ( !isdigit(ch=getchar()) ) if ( ch=='-' ) f=false;
	for ( x=ch^48;isdigit(ch=getchar()); ) x=(x<<1)+(x<<3)+(ch^48);
	return f ? x : -x ;
}
const int maxn=500000+10;
int n,k,h[maxn],cnt;
bool flag[maxn];
long long ds[maxn],us[maxn],max[maxn],max1[maxn],max2[maxn],ans[maxn];
struct edge { int v,w,nxt; } e[maxn<<1];
inline void dfs ( int u,int fr )
{
	for ( int i=h[u];i;i=e[i].nxt ) if ( e[i].v!=fr )
	{
		dfs(e[i].v,u);
		if ( flag[e[i].v] or ds[e[i].v] )
		{
			ds[u]+=e[i].w+ds[e[i].v];
			if ( max1[e[i].v]+e[i].w>max1[u] ) max2[u]=max1[u],max1[u]=max1[e[i].v]+e[i].w;
			else if ( max1[e[i].v]+e[i].w>max2[u] ) max2[u]=max1[e[i].v]+e[i].w;
		}
	}
}
inline void DFS ( int u,int fr )
{
	ans[u]=2*(ds[u]+us[u])-std::max(max1[u],max[u]);
	for ( int i=h[u];i;i=e[i].nxt ) if ( e[i].v!=fr )
	{
		if ( flag[e[i].v] or ds[e[i].v] ) ds[u]-=ds[e[i].v]+e[i].w;
		if ( ds[u] or us[u] ) us[e[i].v]=ds[u]+us[u]+e[i].w;
		if ( max[u] ) max[e[i].v]=max[u]+e[i].w;
		if ( max1[u] )
		{
			if ( max1[e[i].v]+e[i].w<max1[u] ) max[e[i].v]=std::max(max[e[i].v],max1[u]+e[i].w);
			else if ( max2[u] ) max[e[i].v]=std::max(max[e[i].v],max2[u]+e[i].w);
		}
		DFS(e[i].v,u);
		if ( flag[e[i].v] or ds[e[i].v] ) ds[u]+=ds[e[i].v]+e[i].w;
	}
}
signed main()
{
	n=read();k=read();
	for ( int i=1,u,v,w;i<n;i++ ) u=read(),v=read(),w=read(),e[++cnt].nxt=h[u],e[h[u]=cnt].v=v,e[cnt].w=w,e[++cnt].nxt=h[v],e[h[v]=cnt].v=u,e[cnt].w=w;
	for ( int i=1;i<=k;i++ ) flag[read()]=true;
	dfs(1,0);DFS(1,0);
	for ( int i=1;i<=n;i++ ) printf("%lld\n",ans[i]);
}

  

转载于:https://www.cnblogs.com/RenSheYu/p/11318300.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值