NOI 2003 逃学的小孩

提交网址:http://acm.nankai.edu.cn/p1092.html

1092: 逃学的小孩
Time Limit: 1500 ms    Memory Limit: 32000 kB  
Judge type: Multi-cases

Font Style: Aa Aa Aa

Chris家的电话铃响起了,里面传出了Chris的老师焦急的声音:“喂,是Chris的家长吗?你们的孩子又没来上课,不想参加考试了吗?”一听说要考试,Chris的父母就心急如焚,他们决定在尽量短的时间内找到Chris。他们告诉Chris的老师:“根据以往的经验,Chris现在必然躲在朋友ShermieYashiro家里偷玩《拳皇》游戏。现在,我们就从家出发去找Chris,一但找到,我们立刻给您打电话。”说完砰的一声把电话挂了。

Chris居住的城市由N个居住点和若干条连接居住点的双向街道组成,经过街道x需花费Tx分钟。可以保证,任两个居住点间有且仅有一条通路。Chris家在点CShermieYashiro分别住在点A和点BChris的老师和Chris的父母都有城市地图,但Chris的父母知道点ABC的具体位置而Chris的老师不知

为了尽快找到ChrisChris的父母会遵守以下两条规则:

l 如果A距离CB距离C近,那么Chris的父母先去Shermie家寻找Chris,如果找不到,Chris的父母再去Yashiro家;反之亦然

l Chris的父母总沿着两点间唯一的通路行走。

显然,Chris的老师知道Chris的父母在寻找Chris的过程中会遵守以上两条规则,但由于他并不知道ABC的具体位置,所以现在他希望你告诉他,最坏情况下Chris的父母要耗费多长时间才能找到Chris

例如上图,这座城市由4个居住点和3条街道组成,经过每条街道均需花费1分钟时间。假设Chris住在点C,Shermie住在点A,Yashiro住在点B,因为C到B的距离小于C到A的距离,所以Chiris的父母会先去Yashiro家寻找Chris,一旦找不到,再去Shermie家寻找。这样,最坏情况下Chris的父母需要花费4分钟的时间才能找到Chris。

Input

输入的第一行是两个整数N(3 <= N <= 200000)和M,分别表示居住点总数和街道总数。以下M行,每行给出一条街道的信息。第i+1行包含整数UiViTi(1<=Ui,Vi <=N,1 <=Ti <= 1000000000),表示街道i连接居住点UiVi,并且经过街道i需花费Ti分钟。街道信息不会重复给出。

Output

输出仅包含整数T,即最坏情况下Chris的父母需要花费T分钟才能找到Chris。

Sample Input

4 31 2 12 3 13 4 1

Sample Output

4

Hint

数据规模有一定缩减,建议使用scanf 读入数据

Source


下面的讲解来自2007年国家集训队陈瑜希的论文

分析

问题抽象

本题题意很明确,即在一棵树中,每条边都有一个长度值,现要求在树中选择3个点XYZ,满足XY的距离不大于XZ的距离,且XY的距离与YZ的距离之和最大,求这个最大值。

粗略分析

很显然,该题的结构模型是一棵树,而且数据量很大,很容易把这题的方法向在树形结构上使用动态规划上靠拢。考虑任意节点a时,很容易发现,如果以这个点作为题目中要求的节点Y,那么只需要求出离这点最远的两个节点即可。但是在树形结构上,计算出离某个节点最远的两个节点需要的复杂度,而我们并不知道哪个点是答案中的节点Y,所以必须枚举这个点,这样一来,时间复杂度就成了,在N=200000时会严重超时,因此这个方法是不可行的。

枚举Y点的话,会超时,是否就无法加上枚举的思想呢?可以多尝试一些思路。

枚举分叉点

将某个点a当作分叉点时,以其为根构造一棵树,对节点Y,就有两种情况:1Y就是节点a2Ya的某个孩子节点的子树上。对于情况1,可以把它转化为情况2,只需给a加一个空的孩子节点,认为它和a之间的距离是0即可。既然a是分叉点,那么XZ就不能在同一棵子树上,XYYZ也不能在同一棵子树上。题目中要求的是使|XY|+|YZ|最大,也就是要求2|Ya|+|Za|+|Xa|最大。至此,思路已完全明确,对于以a为分叉点的情形,只需求出到a的最远的3个点,而且这3个点分别处于a3棵不同的子树之中。如果采用枚举分叉点的方法的话,每次都需要的计算才行,时间复杂度就又成了。

两次遍历

这里,需要改变一下思路。以点1为根,计算出要求的值后,不去枚举其它的节点,而把这棵树再遍历一遍,进行一次BFS,深度小的先访问,深度大的后访问,就保证了访问到某一个节点的时候,其父亲节点已经被访问过了。假设我们现在访问到了点a,我们现在要求的是距点a3个最远的点,且这3个点到a的路径上不经过除a外的相同节点。显然,这3个点要么位于以a为根的子树中,要么位于以a为根的子树外。如果在以a为根的子树外,那么是一定要通过a的父亲节点与a相连的。至此,思路逐渐清晰起来。此次遍历时,对于点a,检查其父亲节点,只需求出到其父亲节点的最远的,且不在以a为根的子树中的那点即可,再与第一次遍历求得的值进行比较,就可以求出以该点为分叉点时,|XY|+|YZ|的最大值了。具体方法为,每个点记录最大的两个值,并记录这最大的两个值分别是从哪个相邻节点传递来的。当遍历到其某个孩子节点时,只需检查最大值是否是从该孩子节点传递来的,如果是,就取次大值,如果不是,就可以取最大值。这样一来,该算法的时间复杂度和空间复杂度都为,是个非常优秀的算法。

注意

这里提醒大家注意一点,计算过程中的值和最后的结果可能会超过长整型的范围,所以这里需要使用int64或者double类型。


代码是自己写的。

基本思路就是陈瑜希的思路。

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>

using namespace std;

const int maxn=210000;

struct Node{
	int flag;
	long long t;
};

long long Max;
Node cnt[maxn][4];
bool vis[maxn];
vector<Node> son[maxn];

bool cmp(Node a,Node b)
{
	return a.t>b.t;
}

void DFS1(int x)
{
	int i,v;
	for(i=0;i<3;i++)
		cnt[x][i].t=0;
	for(i=0;i<son[x].size();i++)
	{
		v=son[x][i].flag;
		if(!vis[v])
		{
			vis[v]=1;
			DFS1(v);
			cnt[x][3].t=cnt[v][0].t+son[x][i].t;
			cnt[x][3].flag=v;
			sort(cnt[x],cnt[x]+4,cmp);
		}
	}
	if(cnt[x][0].t+2*cnt[x][1].t+cnt[x][2].t>Max)
		Max=cnt[x][0].t+2*cnt[x][1].t+cnt[x][2].t;
}

void DFS2(int x,long long ff)
{
	int i,v;
	for(i=0;i<son[x].size();i++)      //保证父节点先处理完。
	{
		v=son[x][i].flag;
		if(vis[v])
		{
			if(cnt[v][0].flag!=x)
				cnt[x][3].t=cnt[v][0].t;
			else
				cnt[x][3].t=cnt[v][1].t;
			cnt[x][3].flag=v;
			cnt[x][3].t+=ff;
			sort(cnt[x],cnt[x]+4,cmp);
			if(cnt[x][0].t+2*cnt[x][1].t+cnt[x][2].t>Max)
				Max=cnt[x][0].t+2*cnt[x][1].t+cnt[x][2].t;
		}
	}
	for(i=0;i<son[x].size();i++)
	{
		v=son[x][i].flag;
		if(!vis[v])
		{
			vis[v]=1;
			DFS2(v,son[x][i].t);
		}
	}
}

int main()
{
	int N,M;
	int i,u,v;
	long long t;
	Node tmp;

	while(scanf("%d%d",&N,&M)==2)
	{
		for(i=1;i<=N;i++)
			son[i].clear();

		for(i=1;i<=M;i++)
		{
			scanf("%d%d%lld",&u,&v,&t);
			tmp.flag=v;	tmp.t=t;	son[u].push_back(tmp);
			tmp.flag=u;	son[v].push_back(tmp);
		}
		Max=0;
		memset(vis,0,sizeof(vis));
		vis[1]=1;
		DFS1(1);
		memset(vis,0,sizeof(vis));
		vis[1]=1;
		DFS2(1,0);
		printf("%lld\n",Max);
	}

	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值