D48 树的直径 两次DFS+双指针 P3304 [SDOI2013] 直径
D48 树的直径 P3304 [SDOI2013] 直径_哔哩哔哩_bilibili
给定一棵树,直径的长度是多少,所有直径的公共边有多少条。
思路
1. 两次 DFS,先求出一条直径,$pre$ 数组记录这条直径上的点,$l,r$ 记录直径的端点,$mxd$ 记录直径长度。
2. 对直径的点染色,用 col 数组记录,col 数组保证下一步只搜索支路。
3. 从左端点开始向右遍历直径,对直径上的每个点 dfs,求出以这个点 $i$ 为起点的支路最长链的长度 $d[p]$。
双指针收缩路径:
如果支路最长链$=$直径左段长度,则让左指针 $l=i$,因为 $i$ 的左边都不是必经边。
如果支路最长链$=$直径右段长度,则让右指针 $r=i$,此时可以跳出循环,因为 $i$ 的右边的所有边不可能是必经边。
那么 $l$ 到 $r$ 之间的所有边就是必经边了。
时间复杂度为 $O(n)$。
// 树的直径 两次DFS+双指针 O(n) #include<bits/stdc++.h> using namespace std; #define ll long long const int N=200005; int n,p,r,l,pre[N],col[N]; ll mxd,cnt,d[N]; vector<pair<int,int>> e[N]; void dfs(int u,int fa){ if(d[u]>d[p]) p=u; //记录直径端点 pre[u]=fa; //记录路径 for(auto [v,w]:e[u])if(v!=fa&&!col[v]){ d[v]=d[u]+w; dfs(v,u); } } int main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=1,x,y,z;i<n;i++){ cin>>x>>y>>z; e[x].emplace_back(y,z); e[y].emplace_back(x,z); } dfs(1,0); r=p; d[p]=0; dfs(p,0); l=p; mxd=d[p]; for(int i=l;i;i=pre[i]) col[i]=1; //直径的点染色 for(int i=l; i; i=pre[i]){ //双指针收缩路径 ll ld=mxd-d[i], rd=d[i]; p=i,d[p]=0; dfs(p,pre[p]); //搜索支路最长链 if(d[p]==ld) l=i; //支路最长链=直径左段长度 if(d[p]==rd){r=i;break;} //支路最长链=直径右段长度 } for(int i=l;i!=r;i=pre[i]) cnt++; cout<<mxd<<"\n"<<cnt; }
浙公网安备 33010602011771号