题目:poj2631.
题目大意:给出一棵树带边权,找出两个点之间路径边权之和最长的长度.
这道题就是经典的树的直径的问题.
这种问题一般有两种方案做到O(n),一种是树形dp,一种是两遍bfs.
树形dp是用f[i]表示以它为根节点的子树中,离它最远的节点到它的距离.
我们发现,那么以每个节点k为根的子树中必须要有这个节点k的最长链可以表示为d[x]+d[y]+dis[x][k]+dis[y][k],其中dis[i][j]表示i到j这条边的边权,x和y都是k的子节点,且x在k的儿子中d[x]最大,y在k的儿子中d[y]次大.
所以我们可以轻松写出代码:
#include<bits/stdc++.h> //头文件自己去改吧,我改过了交上去是对的 using namespace std; const int N=10000; struct tree{ int y,next,v; }e[N*2]; bool use[N+1]; int top,m,ans,d[N+1],lin[N+1]; void ins(int X,int Y,int V){ e[++top].y=Y;e[top].v=V; e[top].next=lin[X]; lin[X]=top; } inline void into(){ int x,y,v; while (~scanf("%d%d%d",&x,&y,&v)) ++m,ins(x,y,v),ins(y,x,v); } void dfs(int k){ //树形dp use[k]=1; for (int i=lin[k];i;i=e[i].next) if (!use[e[i].y]){ dfs(e[i].y); ans=max(ans,d[k]+d[e[i].y]+e[i].v); d[k]=max(d[k],d[e[i].y]+e[i].v); } } inline void work(){ dfs(1); } inline void outo(){ printf("%d\n",ans); } int main(){ into(); work(); outo(); return 0; }
还有一种做法是两次bfs求树的直径.
这种做法先从任意一个节点(比如说节点1)出发找到距离它最远的节点,将这个节点k作为树的直径的出发点,再次bfs,找到距离节点k最远的节点,作为树的直径的截止点.
然后这两个点之间的距离就是树的直径长度.
证明就略过了.
这种做法比树形dp更容易找到路径,而且效率也是O(n)的.
关于常数,这种做法虽然需要跑两遍,但是没有用到递归,所以常数也较小.
那么代码如下:
//#include<bits/stdc++.h> #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=10000; struct tree{ int y,next,v; }e[N*2]; bool use[N+1]; int top,m,ans,d[N+1],lin[N+1],dis[N+1],h,t,q[N+1]; void ins(int X,int Y,int V){ e[++top].y=Y;e[top].v=V; e[top].next=lin[X]; lin[X]=top; } inline void into(){ int x,y,v; while (~scanf("%d%d%d",&x,&y,&v)) ++m,ins(x,y,v),ins(y,x,v); } int bfs(int start){ //bfs返回距离start最远的点,并计算一个dis数组 int nod=start; memset(use,0,sizeof(use)); h=0;q[t=1]=start;dis[start]=0;use[start]=1; while (h^t) for (int i=lin[q[++h]];i;i=e[i].next) if (!use[e[i].y]) { dis[q[++t]=e[i].y]=dis[q[h]]+e[i].v; use[e[i].y]=1; if (dis[e[i].y]>dis[nod]) nod=e[i].y; } return nod; } inline void work(){ } inline void outo(){ printf("%d\n",dis[bfs(bfs(1))]); } int main(){ into(); work(); outo(); return 0; }