【学习笔记】树的直径

NCC79601

2019-03-06 23:21:10

Personal

这里记录一种由[@杨林树♂](https://www.luogu.org/space/show?uid=117068) 教我的求树的直径的一套方法,解决的问题是[P3304](https://www.luogu.org/problemnew/show/P3304)。 ### 核心思路 直径的定义:一棵树上最长的路径叫做树的直径。可以证明:**树上的直径一定是从与树根距离最大的点,到树根,再到与树根距离次大的点。** 先用$vector$存边 (链式前向星莫名MLE)。 ```cpp #define ll long long struct EDGE { int from, to; ll dis; }; //do it with vector! vector<EDGE> edge[MAXN]; inline void build_edge(int u, int v, ll d) { edge[u].push_back((EDGE){u, v, d}); edge[v].push_back((EDGE){v, u, d}); return; } ``` 对于求直径长度的问题,只需用两次$dfs$即可解决。第一次$dfs$找出直径的一个端点$up$,然后对记录的距离信息$memset$。此后,再以找出的这个端点为树根进行第二次$dfs$找到$down$,就把两个端点都找出来了,它们间的距离即直径长度$D$。 ```cpp inline void init() { dfs1(1, 0); for(register int i=1; i<=n; i++) up = dis[i]>dis[up] ? i : up; memset(dis, 0, sizeof(dis)); dfs1(up, 0); for(register int i=1; i<=n; i++) down = dis[i]>dis[down] ? i : down; D = dis[down]; return; } ``` ### 特殊处理 之后就是解决P3304特色问题了。由于树的直径不一定唯一,所以这道题询问所有直径都经过的边数。 基佬教我的方法是**进行第二次**$dfs$,具体为查找从当前点$u$出发是否还存在长度为$d$的边。可以证明,设当前点$u$离树的一个端点距离为$d$,**假如存在一条不存在于直径中的路径,其长度等于$d$,那么$u$到该端点之间的所有点都不是被所有直径经过的点。** 有了这样一条性质,我们只需要先统计出目前发现的直径上的点数$count$,初始化$l=0,r=count$。枚举目前发现的直径上的点$u$,判断: 1. 当前点$u$到$up$间是否还存在另一条与$u$到$up$距离相等的点,如果存在把$l$更新为自己与$cnt$的较大值,表示从树上第$cnt$个点开始才符合条件; 2. 当前点$u$到$down$间是否还存在另一条与$u$到$down$距离相等的点,如果存在把$r$更新为自己与$cnt$的较小值,表示到树上第$cnt$个点为止才符合条件。最后直接输出$r-l$就行了。 ```cpp bool dfs2(int u, int d) { if(!d) return true; //iterator遍历vector: for(vector<EDGE>::iterator it = edge[u].begin(); it != edge[u].end(); it++) { EDGE o = *it; if(o.to == fa[u]) continue; if(dfs2(o.to, d-o.dis)) return true; } return false; } // 主函数核心代码: int main() { //do something int cnt = 0; for(int i=down; i!=up; i=fa[i]) son[fa[i]] = i, cnt++; int l = 0, r = cnt; cnt = 0; for(register int i=up; i!=down; i=son[i]) { for(vector<EDGE>::iterator it = edge[i].begin(); it!=edge[i].end(); it++) { EDGE o = *it; if(o.to == son[i] || o.to == fa[i]) continue; if(dfs2(o.to, dis[i]-o.dis)) l = max(l, cnt); if(dfs2(o.to, dis[down]-dis[i]-o.dis)) r = min(r, cnt); } cnt++; } } ``` --- ### 完整代码 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; const int MAXN = 200010; struct EDGE { int from, to; ll dis; }; //do it with vector! vector<EDGE> edge[MAXN]; int fa[MAXN], son[MAXN], n, a, b, l; ll dis[MAXN]; int read() { int res = 0, uz = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') uz = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { res = (res<<3) + (res<<1) + (ch^48); ch = getchar(); } return res * uz; } inline void build_edge(int u, int v, ll d) { edge[u].push_back((EDGE){u, v, d}); edge[v].push_back((EDGE){v, u, d}); return; } inline void dfs1(int u, int father) { fa[u] = father; for(vector<EDGE>::iterator it = edge[u].begin(); it != edge[u].end(); it++) { EDGE o = *it; if(o.to == father) continue; dis[o.to] = dis[u] + o.dis; dfs1(o.to, u); } return; } int up, down; ll D; inline void init() { dfs1(1, 0); for(register int i=1; i<=n; i++) up = dis[i]>dis[up] ? i : up; memset(dis, 0, sizeof(dis)); dfs1(up, 0); for(register int i=1; i<=n; i++) down = dis[i]>dis[down] ? i : down; D = dis[down]; return; } bool dfs2(int u, int d) { if(!d) return true; for(vector<EDGE>::iterator it = edge[u].begin(); it != edge[u].end(); it++) { EDGE o = *it; if(o.to == fa[u]) continue; if(dfs2(o.to, d-o.dis)) return true; } return false; } int main() { n = read(); for(register int i=1; i<n; i++) { a = read(); b = read(); l = read(); build_edge(a, b, (ll)l); } init(); int cnt = 0; for(int i=down; i!=up; i=fa[i]) son[fa[i]] = i, cnt++; int l = 0, r = cnt; cnt = 0; for(register int i=up; i!=down; i=son[i]) { for(vector<EDGE>::iterator it = edge[i].begin(); it!=edge[i].end(); it++) { EDGE o = *it; if(o.to == son[i] || o.to == fa[i]) continue; if(dfs2(o.to, dis[i]-o.dis)) l = max(l, cnt); if(dfs2(o.to, dis[down]-dis[i]-o.dis)) r = min(r, cnt); } cnt++; } printf("%lli\n%d", D, r-l); return 0; } //code ``` written at 2019/03/06/13:52