D29 基环树 树的直径 P1399 [NOI2013] 快餐店
视频链接:D29 基环树 P1399 [NOI2013] 快餐店_哔哩哔哩_bilibili
基环树的直径问题:直径不经过环、经过环。经过环要构造前缀后缀,拼凑最优解。
1. 搜索找环,记录环点cv、环边权cw
2. 枚举环点,搜索各树的最大深度d,求出直径最大值 ans1
3. 枚举切断环边,预处理前缀、后缀,链长+树深之和A[],树深+中链+树深之和B[]
4.枚举环边,拼凑最小答案ans2。结果=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; 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 - 洛谷
P12145 [蓝桥杯 2025 省 A] 扫地机器人 - 洛谷
浙公网安备 33010602011771号