BZOJ1999树网的核

题目描述

设T=(V, E, W) 是一个无圈且连通的无向图(也称为无根树),每条边带有正整数的权,我们称T为树网(treenetwork),其中V, E分别表示结点与边的集合,W表示各边长度的集合,并设T有n个结点。路径:树网中任何两结点a,b都存在唯一的一条简单路径,用d(a,b)表示以a,b为端点的路径的长度,它是该路径上各边长度之和。我们称d(a,b)为a,b两结点间的距离。一点v到一条路径P的距离为该点与P上的最近的结点的距离: d(v,P)=min{d(v,u),u为路径P上的结点}。树网的直径:树网中最长的路径称为树网的直径。对于给定的树网T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。偏心距ECC(F):树网T中距路径F最远的结点到路径F的距离,即。 任务:对于给定的树网T=(V,E,W)和非负整数s,求一个路径F,它是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过s(可以等于s),使偏心距ECC(F)最小。我们称这个路径为树网T=(V,E,W)的核(Core)。必要时,F可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。下面的图给出了树网的一个实例。图中,A-B与A-C是两条直径,长度均为20。点W是树网的中心,EF边的长度为5。如果指定s=11,则树网的核为路径DEFG(也可以取为路径DEF),偏心距为8。如果指定s=0(或s=1、s=2),则树网的核为结点F,偏心距为12。

提示

对于70%的数据,n<=200000

对于100%的数据:n<=500000, s<2^31, 所有权值<500

解析:这个题的数据如果和noip一样小就怎么写都行了,然而500000的数据只能考略O(n)或O(logn)的复杂度。分析这道题可以得到这样一个性质:根据直径最长性,如果一点与直径的连线的交点在F外,那么这个点与F的距离一定不是偏心距。有了这个性质,这道题就可以以O(n)的复杂度用单调队列做出来了。

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
deque<int>q;
struct point
{
	int to;
	int next;
	int dis;
}e[1000010];
int n,x,y,z,res=100000010,maxn,s,t,num,ss;
int d[500001],head[500001],f[500001],pre[500001];
bool vis[500001];
void add(int from,int to,int dis)
{
	e[++num].next=head[from];
	e[num].to=to;
	e[num].dis=dis;
	head[from]=num;
}
void dfs(int x)
{
	vis[x]=true;
	for(int i=head[x];i!=0;i=e[i].next)
	{
		int to=e[i].to;
		if(!vis[to])
		{
			d[to]=d[x]+e[i].dis;
			dfs(to);
		}
	}
}
void dfs1(int x)
{
	vis[x]=true;
	for(int i=head[x];i!=0;i=e[i].next)
	{
		int to=e[i].to;
		if(!vis[to])
		{
			pre[to]=x;
			d[to]=d[x]+e[i].dis;
			dfs1(to);
		}
	}
}
void find(int x)
{
	vis[x]=true;
	for(int i=head[x];i!=0;i=e[i].next)
	{
		int to=e[i].to;
		if(!vis[to])
		{
			find(to);
			d[x]=max(d[x],d[to]+e[i].dis);
		}
	}
}
void getf()
{
	int slr=0;
	for(int i=t;i!=0;i=pre[i])
	{
		f[i]=slr;
		for(int j=head[i];j!=0;j=e[j].next)
		{
			int to=e[j].to;
			if(pre[i]==to)
			{
				slr+=e[j].dis;
				break;
			}	
		}
	}
}
int main()
{
	scanf("%d%d",&n,&ss);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	dfs(1);
	for(int i=1;i<=n;i++)
	{
		if(d[i]>maxn)
		{
			maxn=d[i];
			s=i;
		}
	}
	memset(vis,false,sizeof(vis));
	memset(d,0,sizeof(d));
	dfs1(s);
	maxn=0;
	for(int i=1;i<=n;i++)
		if(d[i]>maxn)
		{
			maxn=d[i];
			t=i;
		}
	memset(vis,false,sizeof(vis));
	memset(d,0,sizeof(d));
	for(int i=t;i!=0;i=pre[i])
		vis[i]=true;
	for(int i=t;i!=0;i=pre[i])
		find(i);
	getf();
	int r=t,l=pre[t];
	q.push_front(l);
	if(d[l]<d[r])
		q.push_back(r);
	while(1)
	{
		if(pre[l]!=0&&f[pre[l]]-f[r]<=ss)
		{
			l=pre[l];
			int ans=max(f[r],maxn-f[l]);
			q.push_front(l);
			while(d[q.back()]<d[l])
				q.pop_back();
			ans=max(ans,d[q.back()]);
			res=min(res,ans);
		}
		else if(l!=r)
		{
			if(q.back()==r)
				q.pop_back();
			r=pre[r];
			int ans=max(f[r],maxn-f[l]);
			ans=max(ans,d[q.back()]);
			res=min(res,ans);
		}
		else if(l==r&&pre[l])
		{
			while(!q.empty())
				q.pop_back();
			l=pre[l];
			r=l;
			q.push_front(l);
			int ans=max(f[r],maxn-f[l]);
			ans=max(ans,d[q.back()]);
			res=min(res,ans);
		}
		else
			break;
	}
	printf("%d",res);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值