ACM职业生涯难忘的一道题,现场赛三个人一起卡了三个小时的题。
当时此题现场赛过的人很多,我队三人根据时限判断估计不是一个能取巧推公式的题,当时S10和YaoJinhui主要在纸上模拟找规律,而我其实一开始就很确信最终答案的每条路径都跟叶子节点有关,但对每一个度为1的节点进行搜索极端条件下一定超时,所以一直想优化搜索的策略。
后来离结束还有1h时,由我上机对每个叶子节点暴力搜索一发,但因为只贪心选择了最长的路,而并没有考虑是否形成了一个圈,所以一直没过样例,所以到比赛最后也没有过掉此题。
后来了解到了直径的概念,才知道此题其实甚水,只需要考虑每个节点到直径两点的最大距离,然后累加即可。所以其实比赛时的思路是没错的,只是不需要对所有叶子节点都进行搜索,只需要搜索两个直径端点。
设直径两个端点分别是 rt_1 和 rt_2
代码的思路是先从1节点搜索到距离最远的直径上的节点rt_1,然后再从rt_1搜索找到距其最远的节点rt_2,同时更新每一个点到rt_1的距离,再从rt_2进行搜索更新每一个点到直径节点的距离,然后每个点到直径节点的最大距离进行累加。因直径考虑了两次,故减去一次直径就是最后的答案,三次搜索即可。
1400ms。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef __int64 ll;
const int A = 1e5 + 100;
class P{
public:
int v,next;
ll w;
}G[A*10];
ll Dis[A],Max;
int head[A];
bool vis[A];
int n,tot,rt_1,rt_2;
void add(int u,int v,ll w){
G[tot].v = v;
G[tot].w = w;
G[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u,ll dis){
vis[u] = 1;
if(dis > Max){
Max = dis;
rt_1 = u;
}
for(int i=head[u] ;i!=-1;i=G[i].next){
int v = G[i].v;
ll w = G[i].w;
if(vis[v] == 0){
dfs(v,dis+w);
}
}
}
void Dfs(int u,ll dis,bool type){
vis[u] = 1;
Dis[u] = max(Dis[u],dis);
if(type && dis > Max){
rt_2 = u;
Max = dis;
}
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;
ll w = G[i].w;
if(vis[v] == 0){
Dfs(v,dis+w,type);
}
}
}
void solve(){
rt_1 = rt_2 = 0;
memset(vis,0,sizeof(vis));
Max = 0;
dfs(1,0);
//printf("%I64d rt=%d\n",Max,rt_1);
memset(Dis,0,sizeof(Dis));
memset(vis,0,sizeof(vis));
Max = 0;
Dfs(rt_1,0,1);
//printf("%I64d %I64d rt_1=%d rt_2=%d\n",Dis[rt_1],Dis[rt_2],rt_1,rt_2);
memset(vis,0,sizeof(vis));
Dfs(rt_2,0,0);
//printf("%I64d %I64d rt_1=%d rt_2=%d\n",Dis[rt_1],Dis[rt_2],rt_1,rt_2);
Dis[rt_1] = 0;
ll ans = 0;
for(int i=1 ;i<=n ;i++){
ans += Dis[i];
}
printf("%I64d\n",ans);
}
int main(){
//freopen("input","r",stdin);
while(~scanf("%d",&n)){
memset(head,-1,sizeof(head));
tot = 0;
for(int i=1 ;i<n ;i++){
int u,v;
ll w;
scanf("%d%d%I64d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
solve();
}
return 0;
}