D29 基环树 树的直径 P1399 [NOI2013] 快餐店

视频链接:D29 基环树 P1399 [NOI2013] 快餐店_哔哩哔哩_bilibili

基环树的直径问题:直径不经过环、经过环。经过环要构造前缀后缀,拼凑最优解。

1. 搜索找环,记录环点cv、环边权cw

2. 枚举环点,搜索各树的最大深度d,求出直径最大值 ans1

3. 枚举切断环边,预处理前缀、后缀,链长+树深之和A[],树深+中链+树深之和B[]

4.枚举环边,拼凑最小答案ans2。结果=max(ans1,ans2)/2

P1399 [NOI2013] 快餐店 - 洛谷

// 基环树 树的直径+前后缀 O(n)
#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int n;
int h[N],to[N<<1],ne[N<<1],ww[N<<1],idx;
void add(int a,int b,int c){
  to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx;
}
int vis[N],fa[N],w[N];
int inc[N],cv[N],cw[N],cnt;
double d[N],A[N],B[N],C[N],D[N];
double ans1=0,ans2=1e18;

bool find(int u){ //找环
  vis[u]=1;
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    if(v!=fa[u]){
      fa[v]=u; w[v]=ww[i];      
      if(!vis[v]){
        if(find(v)) return 1;
      }
      else{
        int p=u;
        while(1){ //记录环点,环边权
          inc[p]=1;cv[++cnt]=p;cw[cnt]=w[p];
          p=fa[p];
          if(p==u)break;
        }    
        return 1;
      }
    }
  }
  return 0;
}
void dfs(int u,int ff){ //求各树直径的最大值ans1
  for(int i=h[u];i;i=ne[i]){
    int v=to[i], w=ww[i];
    if(!inc[v]&&v!=ff){
      dfs(v,u);
      ans1=max(ans1,d[u]+d[v]+w);
      d[u]=max(d[u],d[v]+w);
    }
  }
}
int main(){
  scanf("%d",&n);
  for(int i=1,x,y,z;i<=n;i++){
    scanf("%d%d%d",&x,&y,&z);
    add(x,y,z);add(y,x,z);
  }
  find(1); //找环
  for(int i=1;i<=cnt;++i) dfs(cv[i],0); //求各树直径的最大值ans1
  
  double sum=0,mx=0;
  for(int i=1;i<=cnt;++i){ //断边(1,cnt) 求前缀
    sum+=cw[i-1];
    A[i]=max(A[i-1],sum+d[cv[i]]); //链长+树深
    B[i]=max(B[i-1],mx+sum+d[cv[i]]); //树深+中链+树深
    mx=max(mx,d[cv[i]]-sum); //最优增量
  }
  sum=mx=0;
  double cwt=cw[cnt];cw[cnt]=0;
  for(int i=cnt;i>=1;--i){ //断边(1,cnt) 求后缀
    sum+=cw[i];
    C[i]=max(C[i+1],sum+d[cv[i]]);
    D[i]=max(D[i+1],mx+sum+d[cv[i]]);
    mx=max(mx,d[cv[i]]-sum);
  }
  for(int i=1;i<cnt;++i){ //拼凑答案
    ans2=min(ans2,max(max(B[i],D[i+1]),A[i]+cwt+C[i+1]));
  }
  ans2=min(ans2,B[cnt]); //断最后一条边
  printf("%.1lf",max(ans1,ans2)/2);
}

 

// 基环树 树的直径+前后缀 O(n)
#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int n;
int h[N],to[N<<1],ne[N<<1],ww[N<<1],idx;
void add(int a,int b,int c){
  to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx;
}
int vis[N],fa[N],w[N];
int inc[N],cv[N],cw[N],cnt;
double d[N],A[N],B[N],C[N],D[N];
double ans1=0,ans2=1e18;

void find(int u){ //找环
  vis[u]=1;
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    if(v!=fa[u]){
      fa[v]=u; w[v]=ww[i];      
      if(!vis[v]) find(v);
      else{
        int p=u;
        while(1){ //记录环点,环边权
          inc[p]=1;cv[++cnt]=p;cw[cnt]=w[p];
          p=fa[p];
          if(p==u)break;
        }   
      }
    }
  }
}
void dfs(int u,int ff){ //求各树直径的最大值ans1
  for(int i=h[u];i;i=ne[i]){
    int v=to[i], w=ww[i];
    if(!inc[v]&&v!=ff){
      dfs(v,u);
      ans1=max(ans1,d[u]+d[v]+w);
      d[u]=max(d[u],d[v]+w);
    }
  }
}
int main(){
  scanf("%d",&n);
  for(int i=1,x,y,z;i<=n;i++){
    scanf("%d%d%d",&x,&y,&z);
    add(x,y,z);add(y,x,z);
  }
  find(1); //找环
  for(int i=1;i<=cnt;++i) dfs(cv[i],0); //求各树直径的最大值ans1
  
  double sum=0,mx=0;
  for(int i=1;i<=cnt;++i){ //断边(1,cnt) 求前缀
    sum+=cw[i-1];
    A[i]=max(A[i-1],sum+d[cv[i]]); //链长+树深
    B[i]=max(B[i-1],mx+sum+d[cv[i]]); //树深+中链+树深
    mx=max(mx,d[cv[i]]-sum); //最优增量
  }
  sum=mx=0;
  double cwt=cw[cnt];cw[cnt]=0;
  for(int i=cnt;i>=1;--i){ //断边(1,cnt) 求后缀
    sum+=cw[i];
    C[i]=max(C[i+1],sum+d[cv[i]]);
    D[i]=max(D[i+1],mx+sum+d[cv[i]]);
    mx=max(mx,d[cv[i]]-sum);
  }
  for(int i=1;i<cnt;++i){ //拼凑答案
    ans2=min(ans2,max(max(B[i],D[i+1]),A[i]+cwt+C[i+1]));
  }
  ans2=min(ans2,B[cnt]); //断最后一条边
  printf("%.1lf",max(ans1,ans2)/2);
}

  

同题:CF835F Roads in the Kingdom - 洛谷

P11850 [TOIP 2023] 关卡地图 - 洛谷

P12145 [蓝桥杯 2025 省 A] 扫地机器人 - 洛谷

P4381 [IOI 2008] Island - 洛谷

 

posted @ 2022-07-10 23:35  董晓  阅读(530)  评论(0)    收藏  举报