D49 树的直径 两次DFS+双指针 P2491 [SDOI2011] 消防

D49 树的直径 P2491 [SDOI2011] 消防_哔哩哔哩_bilibili

 

P2491 [SDOI2011] 消防 - 洛谷

P1099 [NOIP 2007 提高组] 树网的核 - 洛谷  雷同

给定一棵树,选择一条长度不超过 s 的路径,使得路径外的点到该路径的最大距离最小。输出最大距离最小值。

思路

1. 两次 DFS,先求出一条直径,$pre$ 数组记录这条直径上的点,$l$ 记录直径的左端点,$d$ 数组记录各点到直径右端的距离。

2. 先在直径上搜答案:从左端点开始向右遍历直径,$i$ 指针在右,$j$ 指针在左,二者之间的距离满足 $≤ s$,取 $min(ans,max(d[i],d[l]-d[j]))$。

3. 对直径的点染色,用 col 数组记录,col 数组保证下一步只搜索支路。

4. 再在支路上搜答案:从左端点开始向右遍历直径,对直径上的每个点 dfs,求出以这个点 $i$ 为起点的支路最长链的长度 $d[p]$。取 $max(ans,d[p])$。

// 两次DFS+双指针 O(n)
#include<bits/stdc++.h>
using namespace std;

const int N=300010;
int n,s,p,l,d[N],pre[N],col[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]) continue;
    d[v]=d[u]+w;
    dfs(v,u);
  }
}
int main(){
  ios::sync_with_stdio(0); cin.tie(0);
  cin>>n>>s;
  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); d[p]=0;
  dfs(p,0); l=p;
  
  int ans=2e9;
  for(int i=l,j=l;i;i=pre[i]){ //直径上的答案
    while(d[j]-d[i]>s) j=pre[j];
    ans=min(ans,max(d[i],d[l]-d[j]));
  }
  
  for(int i=l;i;i=pre[i])col[i]=1; //直径染色
  for(int i=l;i;i=pre[i]){ //直径外的答案
    p=i; d[p]=0;
    dfs(i,pre[i]);
    ans=max(ans,d[p]);
  }
  printf("%d\n",ans);
}

 

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