提交网址:http://acm.nankai.edu.cn/p1092.html
Time Limit: 1500 ms Memory Limit: 32000 kB
Judge type: Multi-cases
Chris家的电话铃响起了,里面传出了Chris的老师焦急的声音:“喂,是Chris的家长吗?你们的孩子又没来上课,不想参加考试了吗?”一听说要考试,Chris的父母就心急如焚,他们决定在尽量短的时间内找到Chris。他们告诉Chris的老师:“根据以往的经验,Chris现在必然躲在朋友Shermie或Yashiro家里偷玩《拳皇》游戏。现在,我们就从家出发去找Chris,一但找到,我们立刻给您打电话。”说完砰的一声把电话挂了。
Chris居住的城市由N个居住点和若干条连接居住点的双向街道组成,经过街道x需花费Tx分钟。可以保证,任两个居住点间有且仅有一条通路。Chris家在点C,Shermie和Yashiro分别住在点A和点B。Chris的老师和Chris的父母都有城市地图,但Chris的父母知道点A、B、C的具体位置而Chris的老师不知。
为了尽快找到Chris,Chris的父母会遵守以下两条规则:
l 如果A距离C比B距离C近,那么Chris的父母先去Shermie家寻找Chris,如果找不到,Chris的父母再去Yashiro家;反之亦然
l Chris的父母总沿着两点间唯一的通路行走。
显然,Chris的老师知道Chris的父母在寻找Chris的过程中会遵守以上两条规则,但由于他并不知道A,B,C的具体位置,所以现在他希望你告诉他,最坏情况下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行包含整数Ui、Vi、Ti(1<=Ui,Vi <=N,1 <=Ti <= 1000000000),表示街道i连接居住点Ui和Vi,并且经过街道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个点X、Y、Z,满足X到Y的距离不大于X到Z的距离,且X到Y的距离与Y到Z的距离之和最大,求这个最大值。
粗略分析
很显然,该题的结构模型是一棵树,而且数据量很大,很容易把这题的方法向在树形结构上使用动态规划上靠拢。考虑任意节点a时,很容易发现,如果以这个点作为题目中要求的节点Y,那么只需要求出离这点最远的两个节点即可。但是在树形结构上,计算出离某个节点最远的两个节点需要的复杂度,而我们并不知道哪个点是答案中的节点Y,所以必须枚举这个点,这样一来,时间复杂度就成了,在N=200000时会严重超时,因此这个方法是不可行的。
枚举Y点的话,会超时,是否就无法加上枚举的思想呢?可以多尝试一些思路。
枚举分叉点
将某个点a当作分叉点时,以其为根构造一棵树,对节点Y,就有两种情况:1Y就是节点a;2Y在a的某个孩子节点的子树上。对于情况1,可以把它转化为情况2,只需给a加一个空的孩子节点,认为它和a之间的距离是0即可。既然a是分叉点,那么X和Z就不能在同一棵子树上,X和Y,Y和Z也不能在同一棵子树上。题目中要求的是使|XY|+|YZ|最大,也就是要求2|Ya|+|Za|+|Xa|最大。至此,思路已完全明确,对于以a为分叉点的情形,只需求出到a的最远的3个点,而且这3个点分别处于a的3棵不同的子树之中。如果采用枚举分叉点的方法的话,每次都需要的计算才行,时间复杂度就又成了。
两次遍历
这里,需要改变一下思路。以点1为根,计算出要求的值后,不去枚举其它的节点,而把这棵树再遍历一遍,进行一次BFS,深度小的先访问,深度大的后访问,就保证了访问到某一个节点的时候,其父亲节点已经被访问过了。假设我们现在访问到了点a,我们现在要求的是距点a的3个最远的点,且这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;
}