JZOJ 3919.【NOIP2014模拟11.3】志愿者

本文深入探讨了快乐树形DP算法,一种高效解决特定无根树问题的方法。通过选定关键点作为根节点,利用子树中关键点数量判断,以及两遍深度优先搜索策略,实现了对每个节点出发经过关键点的总代价计算。文章详细解释了算法原理,包括如何求解虚树直径,以及最终答案的计算公式,展示了O(n)时间复杂度的高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DescriptionDescriptionDescription

给定一个大小为nnn的无根树,给定一个大小为KKK的关键点集,求从每个点出发经过这些点的代价和(可以不返回)

数据范围:n≤5×105n\leq 5\times 10^5n5×105


SolutionSolutionSolution

快乐树形dpdpdp

一个非常关键的结论(建议看懂这句话之后再看下面的题解)

从第iii个点出发的答案即为iii与所有关键点组成虚树的长度和的两倍-该虚树直径【后面那个减得原因是因为不能返回】

原因是每条边显然经过两次,除了直径

由于树无根,所以我们选定一个关键点作为根,记为rtrtrt

siz[x]siz[x]siz[x]表示xxx的子树中关键点的个数。作用:判断子树是否存在关键点
len[i]len[i]len[i]虚树所有点到iii的距离
f[i]f[i]f[i]在两遍dfsdfsdfs中有不同的意义,第一遍是指以rtrtrt为根的最长链,第二遍直接表示虚树内直径
g[i]g[i]g[i]直接表示以rtrtrt为根的树上次长链,利用第一遍结束后的f,gf,gf,g可以求直径,当然你也可以多跑两遍dfsdfsdfs来求,做法是相似的
Max_son[x]Max\_son[x]Max_son[x]表示第一遍dfsdfsdfsf[x]f[x]f[x]指向的是哪个儿子,同样应用于求虚树直径

最终答案即为Ansi=2leni−fiAns_i=2len_i-f_iAnsi=2lenifi

时间复杂度:O(n)O(n)O(n)


CodeCodeCode
#include<cctype>
#include<cstdio>
#define LL long long
#define N 500010
using namespace std;int n,k,l[N],tot,Max_son[N],rt;
bool v[N];
struct node{int next,to;LL w;}e[N<<1];
inline void add(int u,int v,LL w){e[++tot]=(node){l[u],v,w};l[u]=tot;return;}
LL ans,z,f[N],g[N],siz[N],len[N];
inline LL read()
{
	char c;LL d=1,f=0;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
inline void dfs1(int x,int fa=-1)
{
	siz[x]=v[x];
	for(register int i=l[x];i;i=e[i].next)
	{
		int y=e[i].to;LL w=e[i].w;
		if(y==fa) continue;
		dfs1(y,x);
		siz[x]+=siz[y];len[x]+=len[y];
		if(siz[y])
		{
			len[x]+=w;
			if(f[y]+w>=f[x]) g[x]=f[x],f[x]=f[y]+w,Max_son[x]=y;
			else if(f[y]+w>=g[x]) g[x]=f[y]+w;
		}
	}
	return;
}
inline void dfs2(int x,int fa=-1)
{
	for(register int i=l[x];i;i=e[i].next)
	{
		int y=e[i].to;LL w=e[i].w,Diam;
		if(y==fa) continue;
		len[y]=len[x];
		if(siz[y]==0) len[y]+=w;
		if(siz[y]==k) len[y]-=w;
		if(Max_son[x]==y) Diam=g[x]+w;//如果这个儿子已经是最长链,则当前Diam=次长链的长度+w
		else Diam=f[x]+w;//否则直接用最长链
		if(Diam>=f[y]) g[y]=f[y],f[y]=Diam,Max_son[y]=0;
		else if(Diam>=g[y]) g[y]=Diam;
		dfs2(y,x);
	}
	return;
}
signed main()
{
	n=read();k=read();
	for(register int i=1,x,y;i<n;i++) x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
	for(register int i=1,x;i<=k;i++) v[rt=read()]=true;
	dfs1(rt);dfs2(rt);
	for(register int i=1;i<=n;i++) printf("%lld\n",2*len[i]-f[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值