D48 树的直径 两次DFS+双指针 P3304 [SDOI2013] 直径

D48 树的直径 P3304 [SDOI2013] 直径_哔哩哔哩_bilibili

 

P3304 [SDOI2013] 直径 - 洛谷

给定一棵树,直径的长度是多少,所有直径的公共边有多少条。

思路

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;
}

 

posted @ 2024-09-10 09:58  董晓  阅读(424)  评论(0)    收藏  举报